文档章节

spring security 应用实例

李德伦
 李德伦
发布于 2014/08/10 12:35
字数 1760
阅读 11815
收藏 53
  1. 开篇说明

最近工作有权限控制的需求,所以看了一下spring的security,它提供了很好的安全服务;

参考文章:http://peiquan.blog.51cto.com/7518552/1384168 ;

在这里我使用第三种权限控制方法,即将用户,权限,资源使用数据库存储,并自定义过滤器,在配置文件里进行相应配置。

二、数据准备

--权限表

CREATE TABLE `authorities` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `authority` varchar(20) DEFAULT NULL,
  `uid` int(11) DEFAULT NULL,  //用户id
  PRIMARY KEY (`id`)
) ;

INSERT INTO `authorities` VALUES ('1', 'ROLE_ADMIN', '1');
INSERT INTO `authorities` VALUES ('2', 'ROLE_USER', '2');

--用户表(密码为123,这里已加密)

CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(20) DEFAULT NULL,
  `password` varchar(60) DEFAULT NULL,
  `enabled` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
);

INSERT INTO `users` VALUES ('1', 'admin', 86888061b399e74e30eeead8c7aab922, '1');
INSERT INTO `users` VALUES ('2', 'user', '368703df04cc8d60e2f494a5c244e45a', '1');

--资源表

CREATE TABLE `demo_resources` (
  `id` int(10) NOT NULL AUTO_INCREMENT,
  `resource_name` varchar(100) NOT NULL,
  `resource_type` varchar(100) NOT NULL,
  `resource_content` varchar(200) NOT NULL,
  `resource_desc` varchar(200) NOT NULL,
  `enabled` int(2) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `resource_name` (`resource_name`),
  KEY `resource_name_2` (`resource_name`)
);

INSERT INTO `demo_resources` VALUES ('1', '所有资源', 'requesturl', '/**', '所有页面',  '1');
INSERT INTO `demo_resources` VALUES ('2', '管理员资源', 'requesturl', '/admin.jsp', '进入管理员页面',  '1');
INSERT INTO `demo_resources` VALUES ('3', 'user资源', 'requesturl', '/', 'user能进入首页',  '1');

--资源与权限关联表

CREATE TABLE `resource_authority` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `rid` int(11) DEFAULT NULL,   //资源id
  `aid` int(11) DEFAULT NULL,  //权限id
  PRIMARY KEY (`id`)
);

INSERT INTO `resource_authority` VALUES ('1', '1', '1');
INSERT INTO `resource_authority` VALUES ('2', '2', '1');
INSERT INTO `resource_authority` VALUES ('3', '3', '2');

上面的数据说明:

       1) admin角色的用户能够访问所有资源(/**,当然我加/admin.jsp这个有点多余,不过没关系) ;

       2) user角色的用户只能进入首页(/);

三、security配置

<http pattern="/login.jsp" security="none" />
  <http auto-config="true" access-denied-page="/403.jsp">
     <form-login login-page="/login.jsp" />  
     <!-- 自定义filter -->  
    <custom-filter before="FILTER_SECURITY_INTERCEPTOR" ref="securityInterceptorFilter" />
  </http>
  
 <!-- 配置认证管理器 -->
 <authentication-manager  alias="authenticationManager">
    <authentication-provider user-service-ref='userDetailsService'>
      <!-- 用户加密解密类  -->  
            <password-encoder hash="md5">
                <salt-source user-property="username"/>  
            </password-encoder> 
    </authentication-provider>
 </authentication-manager> 
 
 
 <beans:bean id="userDetailsService" class="com.springmvc.security.impl.SpringMvcUserDetailsServiceImpl" />
 
 <!-- PasswordEncoder 密码接口 --> 
 <beans:bean id="passwdEcoder" class="org.springframework.security.authentication.encoding.Md5PasswordEncoder"/>
  
 <!-- 元数据提供接口 -->
 <beans:bean id="springMvcInvocationSecurityMetadataSource" class="com.springmvc.security.impl.SpringMvcInvocationSecurityMetadataSourceImpl" >
    
 </beans:bean>
 <!-- 权限抉择接口 -->
 <beans:bean id="accessDecisionManager" class="com.springmvc.security.impl.DemoAccessDecisionManager"/>
 
 <!-- 自定义过滤器 -->  
 <beans:bean id="securityInterceptorFilter" class="com.springmvc.security.impl.DemoSecurityInterceptor">  
     <beans:property name="securityMetadataSource" ref="springMvcInvocationSecurityMetadataSource"/><!-- FilterInvocationSecurityMetadataSource 接口实现类 -->  
     <beans:property name="authenticationManager" ref="authenticationManager"/><!-- 鉴定管理类 -->  
     <beans:property name="accessDecisionManager" ref="accessDecisionManager"/><!-- AccessDecisionManager 接口实现类 -->  
 </beans:bean>

四、功能说明

    1) springMvcInvocationSecurityMetadataSource

     服务器启动时,会将数据库中所有权限和资源提取出来,放在一个map里,等用户登录到该系统时,就会使用到map,从而判断该用户是否有这个权限。

 public class SpringMvcInvocationSecurityMetadataSourceImpl implements
  FilterInvocationSecurityMetadataSource {
 private static final Logger logger = LoggerFactory
   .getLogger(SpringMvcInvocationSecurityMetadataSourceImpl.class);
 private SecurityServiceInf securityService;
 @Autowired
 public SpringMvcInvocationSecurityMetadataSourceImpl(
   SecurityServiceInf securityService) {
  this.securityService = securityService;
  initResources();
 }
 // 所有的资源和权限的映射就存在这里
 private HashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap = new HashMap<RequestMatcher, Collection<ConfigAttribute>>();
 private Collection<ConfigAttribute> allAttribute = new HashSet<ConfigAttribute>();
 /**
  * 初始化所有的资源,这个会在容器运行的时候的构造方法里调用
  */
 private void initResources() {
  logger.debug("init SecurityMetadataSource load all resources");
  // 读取所有的资源,和资源相关联的的权限
  // 读取所有权限点
  Collection<AuthorityEntity> allAuthority = securityService
    .getAllAuthority();
  logger.debug("start to convert AUthortiyEntity to SercurityConfig");
  for (AuthorityEntity authEntity : allAuthority) {
   String authString = authEntity.getAuthority();
   logger.debug("add authroity named:[" + authString + "]");
   SecurityConfig attrConfig = new SecurityConfig(authString);
   allAttribute.add(attrConfig);
  }
  // 读取所有资源
  Collection<ResourceEntity> allResources = securityService
    .findAllResources();
  // 循环所有资源
  for (ResourceEntity resourceEntiry : allResources) {
   // 按照资源查询和资源相关的权限点
   Collection<AuthorityEntity> authEntities = securityService
     .getAuthorityByResource(resourceEntiry.getId());
   // 把此关系保存到requestMap里
   // 获取资源
   String resourceContent = resourceEntiry.getResourceContent();
   // 把url资源转化为一个spring的工具类,请求匹配器类
   logger.debug("add new requestmatcher with [" + resourceContent
     + "]");
   RequestMatcher matcher = new AntPathRequestMatcher(resourceContent);
   // 循环权限 定义一个权限的集合,和此资源对应起来,添加到HashMap里
   Collection<ConfigAttribute> array = new ArrayList<ConfigAttribute>(
     authEntities.size());
   for (AuthorityEntity auth : authEntities) {
    // 转化权限对象为SecurityConfig
    SecurityConfig securityConfig = new SecurityConfig(
      auth.getAuthority());
    array.add(securityConfig);
   }
   requestMap.put(matcher, array);
  }
 }
 /**
  * 根据资源获取需要的权限名称
  */
 @Override
 public Collection<ConfigAttribute> getAttributes(Object object)
   throws IllegalArgumentException {
  logger.debug("get resource " + object + " authority");
  // 把对象转化为请求
  final HttpServletRequest request = ((FilterInvocation) object)
    .getRequest();
  // 循环整个Map 看看有没有可以匹配的,如果有匹配的就立刻返回
  Collection<ConfigAttribute> attrHashMap = new HashSet<ConfigAttribute>();
  for (Map.Entry<RequestMatcher, Collection<ConfigAttribute>> entry : requestMap
    .entrySet()) {
   if (entry.getKey().matches(request)) {
    logger.debug("request matches :" + request.getRequestURL());
    attrHashMap.addAll(entry.getValue());
   }
  }
  if (attrHashMap.size() > 0) {
   // 如果有匹配的就转成ArrayList,然后返回list
   Collection<ConfigAttribute> attr = new ArrayList<ConfigAttribute>(
     attrHashMap);
   return attr;
  }
  logger.debug("request no matches");
  return Collections.emptyList();
 }
 /**
  * 获取所有权限点
  */
 @Override
 public Collection<ConfigAttribute> getAllConfigAttributes() {
  return this.allAttribute;
 }
 @Override
 public boolean supports(Class<?> clazz) {
  // TODO Auto-generated method stub
  return true;
 }
}

  requestMap里的数据如下:

 

  2) userDetailsService

  当用户登录时,会使用输入的用户信息,与数据库中的比较,用户名错误或密码错误,都会有相应的提示(下面会有介绍),都正确的话,会返回一个user实体。

 public class SpringMvcUserDetailsServiceImpl implements UserDetailsServiceInf {
 @Autowired
 private DemoAuthorityRepository authRepository;
 @Autowired
 private UserRepository demoUserReposiroty;
 @Override
 public UserDetails loadUserByUsername(String username)
   throws UsernameNotFoundException {
  // 读取用户
  UsersEntity userEntity = demoUserReposiroty.findByName(username);
  // 读取权限
  Collection<GrantedAuthority> auths = new ArrayList<GrantedAuthority>();
  // 这里需要从数据库里读取所有的权限点
  Collection<com.springmvc.model.AuthorityEntity> aes = authRepository
    .getAuthorityByUser(userEntity.getId());
  for (AuthorityEntity ae : aes) {
   auths.add(new SimpleGrantedAuthority(ae.getAuthority()));
  }
  User user = new User(userEntity.getUsername(),
    userEntity.getPassword(), true, true, true, true, auths);
  return user;
 }
}

   3) accessDecisionManager

   判断当前用户是否拥有访问该资源的权限。

 public class DemoAccessDecisionManager implements AccessDecisionManager {
 @Override
 public void decide(Authentication authentication, Object object,
   Collection<ConfigAttribute> configAttributes)
   throws AccessDeniedException, InsufficientAuthenticationException {
  if (null == configAttributes) {
   return;
  }
  Iterator<ConfigAttribute> cons = configAttributes.iterator();
  while (cons.hasNext()) {
   ConfigAttribute ca = cons.next();
   String needRole = ((SecurityConfig) ca).getAttribute();
   // gra 为用户所被赋予的权限,needRole为访问相应的资源应具有的权限
   for (GrantedAuthority gra : authentication.getAuthorities()) {
    if (needRole.trim().equals(gra.getAuthority().trim())) {
     return;
    }
   }
  }
  throw new AccessDeniedException("没有权限");
 }
 @Override
 public boolean supports(ConfigAttribute attribute) {
  // TODO Auto-generated method stub
  return true;
 }
 @Override
 public boolean supports(Class<?> clazz) {
  // TODO Auto-generated method stub
  return true;
 }
}

五、权限控制

  1) 登录

    若使用security默认的登录页,则登录时的错误提示信息是在spring-security-core包下面的messages.properties等;

    但是一般我们使用自己的登录页,上面security.xml已配置了登陆页的路径login.jsp,那么提示信息就得自己配置了,可以自定义message_zh_CN.properties,放在根路径下的message包里,然后这样配置:

 <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
    <property name="defaultEncoding" value="UTF-8" />
    <property name="basenames">
       <list>
          <value>classpath:message/message</value>
       </list>
    </property>
 </bean>

  message_zh_CN.properties信息如下:

 AbstractUserDetailsAuthenticationProvider.badCredentials=\u5BC6\u7801\u4E0D\u6B63\u786E

  security默认提示:坏的凭证,这里是'密码不正确',当然你可以改成任何提示信息;

  若登录时用户名错误,返回的信息是no entity found....

  下面使用错误密码登录,提示信息如下:

 

  比较密文的代码如下:

 

 

 2) 登录成功后,访问资源

      i. 使用admin账号登录,然后访问admin.jsp

   

    

  

     ii. 使用user账号登录,然后访问admin.jsp

  

   

   可以看到,user无权访问admin.jsp。

ok,只要权限和资源关系配置好,security会帮我们自动拦截,实现权限控制。

  

© 著作权归作者所有

上一篇: spring email
李德伦
粉丝 19
博文 15
码字总数 9424
作品 0
海淀
程序员
私信 提问
加载中

评论(10)

李德伦
李德伦 博主

引用来自“Agreas”的评论

com.springmvc.security.impl.DemoSecurityInterceptor 这个是怎么写的?有代码么
import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import org.springframework.security.access.SecurityMetadataSource; import org.springframework.security.access.intercept.AbstractSecurityInterceptor; import org.springframework.security.access.intercept.InterceptorStatusToken; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; /** * 自定义的安全过滤器 *

* Title: DemoSecurityInterceptor *

*

* Description: *

*

*

*/ public class DemoSecurityInterceptor extends AbstractSecurityInterceptor implements Filter { /** * 声明 */ private FilterInvocationSecurityMetadataSource securityMetadataSource; /** * 声明 */ private FilterInvocation fi; /** * 声明 */ private InterceptorStatusToken token; @Override public void init(FilterConfig filterConfig) throws ServletException { // TODO Auto-generated method stub } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { fi = new FilterInvocation(request, response, chain); invoke(fi); } /** * * * @param fi 参数 * @throws IOException io异常 * @throws ServletException servlet异常 */ public void invoke(FilterInvocation fi) throws IOException, ServletException { token = super.beforeInvocation(fi); try { fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } catch (Exception e) { super.afterInvocation(token, null); } } @Override public void destroy() { // TODO Auto-generated method stub } @Override public Class getSecureObjectClass() { return FilterInvocation.class; } @Override public SecurityMetadataSource obtainSecurityMetadataSource() { return this.securityMetadataSource; } /** * FilterInvocationSecurityMetadataSource get方法 * * @return securityMetadataSource */ public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() { return securityMetadataSource; } /** * FilterInvocationSecurityMetadataSource set方法 * * @param securityMetadataSource set方法 */ public void setSecurityMetadataSource(FilterInvocationSecurityMetadataSource securityMetadataSource) { this.securityMetadataSource = securityMetadataSource; } }
Agreas
Agreas
com.springmvc.security.impl.DemoSecurityInterceptor 这个是怎么写的?有代码么
李德伦
李德伦 博主

引用来自“把苹果啃哭了”的评论

如果这个类是服务器启动时加载的,那么权限和资源变化后,新建立的关系就不能被使用,并不能反映的新的资源与权限的map中,这个问题您这边是如何处理的。
权限改变后,要重新加载这个map
把苹果啃哭了
把苹果啃哭了
如果这个类是服务器启动时加载的,那么权限和资源变化后,新建立的关系就不能被使用,并不能反映的新的资源与权限的map中,这个问题您这边是如何处理的。
李德伦
李德伦 博主

引用来自“romatnic_PK”的评论

楼主,我最近在研究spring security,可以发一份你的完整spring security3代码给我吗?谢谢!
380824406@qq.com
这是公司的项目,完整的代码肯定不能给你啊,上面贴的就是主要的,你可以试着做一下,哪里不懂,Google一下或问我都可以
r
romatnic_PK
楼主,我最近在研究spring security,可以发一份你的完整spring security3代码给我吗?谢谢!
380824406@qq.com
r
romatnic_PK
楼主,我最近在研究spring security,可以发一份你的完整spring security3代码给我吗?谢谢!
李德伦
李德伦 博主

引用来自“似水-年华”的评论

SecurityServiceInf 跟 AuthorityEntity 这两个类的代码也贴出来 要不然没法研究啊
AuthorityEntity就是权限实体
李德伦
李德伦 博主

引用来自“似水-年华”的评论

SecurityServiceInf 跟 AuthorityEntity 这两个类的代码也贴出来 要不然没法研究啊
public interface SecurityServiceInf { /** * *

* Title: getAllAuthority *

*

* Description: 获取所有的权限实体 *

* * @return AuthorityEntity */ Collection getAllAuthority(); /** * *

* Title: getAllResources *

*

* Description: 获取所有权限资源实体类集合 *

* * @return ResourceEntity */ Collection getAllResources(); /** * *

* Title: getAuthorityByResource *

*

* Description: 根据资源获取所有的权限点 *

* * @param resourceEntiry 资源 * @return AuthorityEntity */ Collection getAuthorityByResource( ResourceEntity resourceEntiry); }
似水-年华
似水-年华
SecurityServiceInf 跟 AuthorityEntity 这两个类的代码也贴出来 要不然没法研究啊
Spring Boot 2 实战:使用 Spring Boot Admin 监控你的应用

前言 生产上对 应用 的监控是十分必要的。我们可以近乎实时来对应用的健康、性能等其他指标进行监控来及时应对一些突发情况。避免一些故障的发生。对于 Spring Boot 应用来说我们可以通过一个...

码农小胖哥
10/20
34
0
Spring Security总结

简介 Spring Security是一个强大的和高度可定制的身份验证和访问控制框架,它的前身是 Acegi Security。Spring Security着重于为Java应用程序提供身份验证和授权。提供了一组可以在Spring应用...

asdf08442a
2018/09/28
200
1
更加优雅地配置Spring Securiy(使用Java配置和注解)

Spring Security 借助一系列Servlet Filter 来提供安全性功能,但是借助Spring的小技巧,我们只需要配置一个Filer就可以了,DelegatingFilterProxy是一个特殊的Servlet Filter,它本身所做的...

小小庄
2016/10/14
0
0
Spring Security的配置使用

1.Spring Security简要介绍 Spring Security以前叫做acegi,是后来才成为Spring的一个子项目,也是目前最为流行的一个安全权限管理框架,它与Spring紧密结合在一起。 Spring Security关注的重...

程序界小强
01/17
0
0
Spring安全权限管理(Spring Security)

1.Spring Security简要介绍 Spring Security以前叫做acegi,是后来才成为Spring的一个子项目,也是目前最为流行的一个安全权限管理框架,它与Spring紧密结合在一起。 Spring Security关注的重...

阿丢丢
2014/04/28
318
0

没有更多内容

加载失败,请刷新页面

加载更多

PostgreSQL 11.3 locking

rudi
今天
5
0
Mybatis Plus sql注入器

一、继承AbstractMethod /** * @author beth * @data 2019-10-23 20:39 */public class DeleteAllMethod extends AbstractMethod { @Override public MappedStatement injectMap......

一个yuanbeth
今天
10
1
一次写shell脚本的经历记录——特殊字符惹的祸

本文首发于微信公众号“我的小碗汤”,扫码文末二维码即可关注,欢迎一起交流! redis在容器化的过程中,涉及到纵向扩pod实例cpu、内存以及redis实例的maxmemory值,statefulset管理的pod需要...

码农实战
今天
4
0
为什么阿里巴巴Java开发手册中不建议在循环体中使用+进行字符串拼接?

之前在阅读《阿里巴巴Java开发手册》时,发现有一条是关于循环体中字符串拼接的建议,具体内容如下: 那么我们首先来用例子来看看在循环体中用 + 或者用 StringBuilder 进行字符串拼接的效率...

武培轩
今天
8
0
队列-链式(c/c++实现)

队列是在线性表功能稍作修改形成的,在生活中排队是不能插队的吧,先排队先得到对待,慢来得排在最后面,这样来就形成了”先进先出“的队列。作用就是通过伟大的程序员来实现算法解决现实生活...

白客C
今天
81
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部