spring security4.2.3 登录权限资源控制框架
1. 原理:使用众多的拦截器对url拦截,以此来管理权限。但是这么多拦截器,笔者不可能对其一一来讲,主要讲里面核心流程的两个。
首先,权限管理离不开登陆验证的,所以登陆验证拦截器AuthenticationProcessingFilter要讲;
还有就是对访问的资源管理吧,所以资源管理拦截器AbstractSecurityInterceptor要讲;
但拦截器里面的实现需要一些组件来实现,所以就有了AuthenticationManager、accessDecisionManager等组件来支撑。
现在先大概过一遍整个流程,用户登陆,会被AuthenticationProcessingFilter拦截,调用AuthenticationManager的实现,而且AuthenticationManager会调用ProviderManager来获取用户验证信息(不同的Provider调用的服务不同,因为这些信息可以是在数据库上,可以是在LDAP服务器上,可以是xml配置文件上等),如果验证通过后会将用户的权限信息封装一个User放到spring的全局缓存SecurityContextHolder中,以备后面访问资源时使用。
访问资源(即授权管理),访问url时,会通过AbstractSecurityInterceptor拦截器拦截,其中会调用FilterInvocationSecurityMetadataSource的方法来获取被拦截url所需的全部权限,在调用授权管理器AccessDecisionManager,这个授权管理器会通过spring的全局缓存SecurityContextHolder获取用户的权限信息,还会获取被拦截的url和被拦截url所需的全部权限,然后根据所配的策略(有:一票决定,一票否定,少数服从多数等),如果权限足够,则返回,权限不够则报错并调用权限不足页面。
直接上代码
[code]1.springsecurity.xml <!-- 用户的密码加密或解密 --> <bean id="passwordEncoder" class="com.whggbd.bsz.secutiry.MyPasswordEncoder" /> <bean id="authenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider"> <property name="userDetailsService" ref="userDetailsService" /> <property name="hideUserNotFoundExceptions" value="false" /> <property name="passwordEncoder" ref="passwordEncoder" /> </bean> <!-- 认证配置, 使用userDetailsService提供的用户信息 --> <s:authentication-manager alias="authenticationManager"> <s:authentication-provider ref="authenticationProvider" /> </s:authentication-manager> <!-- 访问决策器,决定某个用户具有的角色,是否有足够的权限去访问某个资源。 --> <bean id="accessDecisionManager" class="com.whggbd.bsz.secutiry.MyAccessDecisionManager" /> <!-- 一个自定义的filter,必须包含authenticationManager, accessDecisionManager,securityMetadataSource三个属性。 --> <bean id="myFilter" class="com.whggbd.bsz.secutiry.MyFilterSecurityInterceptor"> <!-- 资源数据源 --> <property name="securityMetadataSource" ref="mySecurityMetadataSource" /> <!-- 认证管理器 --> <property name="authenticationManager" ref="authenticationManager" /> <!-- 访问决策器 --> <property name="accessDecisionManager" ref="accessDecisionManager" /> </bean> <!-- 重写登陆验证 --> <bean id="loginFilter" class="com.whggbd.bsz.secutiry.MyUsernamePasswordAuthenticationFilter"> <!-- 处理登陆的Action --> <property name="filterProcessesUrl" value="/j_spring_security_check"></property> <!-- 验证成功后的跳转 --> <property name="authenticationSuccessHandler" ref="loginLogAuthenticationSuccessHandler"></property> <!-- 验证失败后的处理 --> <property name="authenticationFailureHandler" ref="simpleUrlAuthenticationFailureHandler"></property> <!-- <property name="sessionAuthenticationStrategy" ref="sessionAuthenticationStrategy" /> --> <!-- 认证管理 --> <property name="authenticationManager" ref="authenticationManager"></property> </bean> <!-- 登陆成功 --> <bean id="loginLogAuthenticationSuccessHandler" class="com.whggbd.bsz.secutiry.MySimpleUrlAuthenticationSuccessHandler"> <!--<property name="alwaysUseDefaultTargetUrl" value="true"/> <property name="defaultTargetUrl" value="/views/desktop.jsp" /> <property name="targetUrlParameter" value="redirectTo" /> --> </bean> <!-- 登陆失败 --> <bean id="simpleUrlAuthenticationFailureHandler" class="com.whggbd.bsz.secutiry.MySimpleUrlAuthenticationFailureHandler"> <!-- 可以配置相应的跳转方式。属性forwardToDestination为true采用forward false为sendRedirect --> <!-- <property name="defaultFailureUrl" value="/failure"></property> --> </bean> <!-- Spring Security 认证切入点 --> <bean id="loginUrlEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint"> <constructor-arg name="loginFormUrl" value="/views/index.jsp" /> </bean> <!-- 不要过滤图片等静态资源,其中**代表可以跨越目录,*不可以跨越目录。 --> <s:http pattern="/**/*.ico" security="none" /> <s:http pattern="/**/*.svg" security="none" /> <s:http pattern="/**/*.jpg" security="none" /> <s:http pattern="/**/*.png" security="none" /> <s:http pattern="/**/*.gif" security="none" /> <s:http pattern="/**/*.css" security="none" /> <s:http pattern="/**/*.css.map" security="none" /> <s:http pattern="/**/*.js" security="none" /> <s:http pattern="/views/index.jsp" security="none" /> <s:http pattern="/views/app/login/login.jsp" security="none" /> <s:http pattern="/**/*.docx" security="none" /> <s:http pattern="/**/*.doc" security="none" /> <s:http pattern="/views/template/alarmInfo1.jsp" security="none" /> <s:http pattern="/views/app/overview/surveyStationSimpleVer.jsp" security="none" /> <s:http auto-config="false" use-expressions="true" entry-point-ref="loginUrlEntryPoint"> <s:headers> <s:frame-options disabled="true"/> </s:headers> <!-- 权限判断、处理的filter链 --> <s:custom-filter before="FILTER_SECURITY_INTERCEPTOR" ref="myFilter" /> <!-- 处理用户登陆时,用户名和密码判断的filter(重要) --> <s:custom-filter position="FORM_LOGIN_FILTER" ref="loginFilter" /> <s:session-management invalid-session-url="/sessionTimeout" /> <!-- 登出 --> <!-- <s:logout delete-cookies="JSESSIONID" logout-url="/logout" invalidate-session="true" logout-success-url="/" /> --> <s:csrf disabled="true" /> </s:http> 2.UserDetail.class package com.whggbd.bsz.secutiry; import com.whggbd.bsz.entity.TSysUser; import java.util.Collection; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import com.whggbd.bsz.entity.TSysUser; /** * 权限管理使用user对象:必须实现userDetails接口 */ public class UserDetail implements UserDetails { private Collection<GrantedAuthority> authorities; private TSysUser user; // private String password; // private String username; private boolean isAccountNonExpired; private boolean isAccountNonLocked; private boolean isCredentialsNonExpired; private boolean isEnabled; /** default constructor */ public UserDetail() { } public UserDetail(TSysUser user, boolean isAccountNonExpired, boolean isAccountNonLocked, boolean isCredentialsNonExpired, boolean isEnabled, Collection<GrantedAuthority> authorities) { this.user=user; // this.username = username; // this.password = password; this.isAccountNonExpired = isAccountNonExpired; this.isAccountNonLocked = isAccountNonLocked; this.isCredentialsNonExpired = isCredentialsNonExpired; this.isEnabled = isEnabled; this.authorities = authorities; } // Constructors public Collection<GrantedAuthority> getAuthorities() { return authorities; } public void setAuthorities(Collection<GrantedAuthority> authorities) { this.authorities = authorities; } public String getPassword() { return user.getUSER_PASSWORD(); } public TSysUser getUser() { return user; } public void setUser(TSysUser user) { this.user = user; } public String getUsername() { return user.getUSER_NAME(); } public boolean isAccountNonExpired() { return isAccountNonExpired; } public void setAccountNonExpired(boolean isAccountNonExpired) { this.isAccountNonExpired = isAccountNonExpired; } public boolean isAccountNonLocked() { return isAccountNonLocked; } public void setAccountNonLocked(boolean isAccountNonLocked) { this.isAccountNonLocked = isAccountNonLocked; } public boolean isCredentialsNonExpired() { return isCredentialsNonExpired; } public void setCredentialsNonExpired(boolean isCredentialsNonExpired) { this.isCredentialsNonExpired = isCredentialsNonExpired; } public boolean isEnabled() { return isEnabled; } public void setEnabled(boolean isEnabled) { this.isEnabled = isEnabled; } // @Override // public boolean equals(Object obj) { // System.out.println("进入equals方法"); // if (obj instanceof UserDetail) { // UserDetail another = (UserDetail)obj; // return this.getUsername().equals(another.getUsername()); // }else{ // return false; // } // } // // @Override // public int hashCode() { // System.out.println("进入hashCode方法"); // return this.getUsername().hashCode(); // } } 3.MyUsernamePasswordAuthenticationFilter.class package com.whggbd.bsz.secutiry; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; /** * 重写用户名密码验证部分,加入了验证码的验证 */ public class MyUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter { @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { System.out.println("MyUsernamePasswordAuthenticationFilter.attemptAuthentication()............"); try { Authentication authentication = super.attemptAuthentication(request, response); return authentication; } catch (UsernameNotFoundException e) { throw new AuthenticationServiceException(e.getMessage()); } catch (Exception e) { throw new AuthenticationServiceException("用户名或密码不正确!"); } } } 3.ETUserDetailService.class package com.whggbd.bsz.secutiry; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import org.springframework.beans.factory.annotation.Autowired; import o 20000 rg.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import com.google.common.collect.Sets; import com.whggbd.bsz.entity.TSysMenu; import com.whggbd.bsz.entity.TSysUser; import com.whggbd.bsz.service.UserMapperService; @Service("userDetailsService") public class ETUserDetailService implements UserDetailsService { @Autowired private UserMapperService userMapperService; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { TSysUser sysUser = userMapperService.getUserByUserName(username); if (sysUser == null) { System.out.println("username is " + username); throw new UsernameNotFoundException(username); } Collection<GrantedAuthority> auths = Sets.newHashSet(); List<TSysMenu> sysMenu =userMapperService.getMenusByUserId(new Long((long)sysUser .getUSER_ID())); for (TSysMenu items : sysMenu) { auths.add(new SimpleGrantedAuthority(items.getDISPLAY_NAME())); } return new UserDetail(sysUser, true,true, true, true, auths); } } 4.InvocationSecurityMetadataSourceServiceImpl package com.whggbd.bsz.secutiry; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.annotation.Resource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; import org.springframework.stereotype.Service; import org.springframework.util.AntPathMatcher; import com.google.gson.Gson; import com.whggbd.bsz.entity.TSysMenu; import com.whggbd.bsz.entity.TSysRole; import com.whggbd.bsz.entity.TSysUser; import com.whggbd.bsz.service.UserMapperService; @Service("mySecurityMetadataSource") public class InvocationSecurityMetadataSourceServiceImpl implements FilterInvocationSecurityMetadataSource { private List<TSysMenu> sysMenus; @Autowired private UserMapperService userMapperService; private static Map<String, Collection<ConfigAttribute>> resourceMap = new ConcurrentHashMap<String, Collection<ConfigAttribute>>(); //private AntPathMatcher urlMatcher = new AntPathMatcher(); public InvocationSecurityMetadataSourceServiceImpl() { super(); sysMenus = new ArrayList<TSysMenu>(); } @Override public Collection<ConfigAttribute> getAllConfigAttributes() { return null; } private void loadResourcesDefine() { System.out.println("MySecurityMetadataSource.loadResourcesDefine()--------------开始加载资源列表数据--------"); if (sysMenus.size() == 0) { sysMenus = userMapperService.getMenus(); } for (TSysMenu sysMenu : sysMenus) { // Collection<ConfigAttribute> configAttributes = null; String url = sysMenu.getURL(); Collection<ConfigAttribute> value = null; ConfigAttribute configAttribute = new SecurityConfig( sysMenu.getDISPLAY_NAME()); if (resourceMap.containsKey(url)) { value = resourceMap.get(url); value.add(configAttribute); resourceMap.put(url, value); } else { value = new ArrayList<ConfigAttribute>(); value.add(configAttribute); resourceMap.put(url, value); } } // System.out.println("权限有:" + new Gson().toJson(resourceMap)); } /* * 根据请求的资源地址,获取它所拥有的权限 */ @Override public Collection<ConfigAttribute> getAttributes(Object obj) throws IllegalArgumentException { // object 是一个URL,被用户请求的url。 this.loadResourcesDefine(); String url = ((FilterInvocation) obj).getRequestUrl(); System.out.println("MySecurityMetadataSource:getAttributes()---------------请求地址为:" + url); // Pattern p = Pattern.compile("/views"); // Matcher m = p.matcher(url); // if(m.find()){ // // ?所在的索引位 // int firstQuestionMarkIndex = url.indexOf("?"); // //存在就截取 // if (firstQuestionMarkIndex != -1) // { // url = url.substring(0, firstQuestionMarkIndex); // } // url集合 Iterator<String> ite = resourceMap.keySet().iterator(); while (ite.hasNext()) { String resURL = ite.next(); if (url.indexOf(resURL) != -1) { System.out.println("所需权限是:" + new Gson().toJson(resourceMap.get(resURL))); return resourceMap.get(resURL); } } // } return null; } @Override public boolean supports(Class<?> arg0) { System.out .println("MySecurityMetadataSource.supports()---------------------"); return true; } } 5.MyFilterSecurityInterceptor.class package com.whggbd.bsz.secutiry; 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; public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter { //与applicationContext-security.xml里的myFilter的属性securityMetadataSource对应, //其他的两个组件,已经在AbstractSecurityInterceptor定义 private FilterInvocationSecurityMetadataSource securityMetadataSource; @Override public SecurityMetadataSource obtainSecurityMetadataSource() { return this.securityMetadataSource; } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { FilterInvocation fi = new FilterInvocation(request, response, chain); invoke(fi); } private void invoke(FilterInvocation fi) throws IOException, ServletException { // object为FilterInvocation对象 //super.beforeInvocation(fi);源码 //1.获取请求资源的权限 //执行Collection<ConfigAttribute> attributes = SecurityMetadataSource.getAttributes(object); //2.是否拥有权限 //this.accessDecisionManager.decide(authenticated, object, attributes); InterceptorStatusToken token = super.beforeInvocation(fi); try { fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } finally { super.afterInvocation(token, null); } } public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() { return securityMetadataSource; } public void setSecurityMetadataSource(FilterInvocationSecurityMetadataSource securityMetadataSource) { this.securityMetadataSource = securityMetadataSource; } public void init(FilterConfig arg0) throws ServletException { // TODO Auto-generated method stub } public void destroy() { // TODO Auto-generated method stub } @Override public Class<? extends Object> getSecureObjectClass() { //下面的MyAccessDecisionManager的supports方面必须放回true,否则会提醒类型错误 return FilterInvocation.class; } } 6.MyPasswordEncoder.class package com.whggbd.bsz.secutiry; import org.springframework.security.authentication.encoding.PasswordEncoder; /** * */ @SuppressWarnings("deprecation") public class MyPasswordEncoder implements PasswordEncoder { @Override public String encodePassword(String pwd, Object arg1) { return pwd; } @Override public boolean isPasswordValid(String npwd, String opwd, Object arg2) { System.out.println("MyPasswordEncoder.isPasswordValid()..................."); return npwd.equals(opwd); } } 7.MyAccessDecisionManager.class package com.whggbd.bsz.secutiry; import java.io.IOException; import java.util.Collection; import java.util.Iterator; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.security.access.AccessDecisionManager; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.authentication.InsufficientAuthenticationException; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.web.access.AccessDeniedHandler; public class MyAccessDecisionManager implements AccessDecisionManager { public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException { System.out .println("MyAccessDecisionManager.decide().................."); // 如果权限列表为空 if (null == configAttributes) return; // 权限列表 Iterator<ConfigAttribute> it = configAttributes.iterator(); while (it.hasNext()) { ConfigAttribute ca = it.next(); // 权限 // 相应的权限 String needRole = ((SecurityConfig) ca).getAttribute(); // ga 为用户所被赋予的权限 // needRole 为访问相应的资源应该具有的权限。 for (GrantedAuthority ga : authentication.getAuthorities()) { if (needRole.trim().equals(ga.getAuthority().trim())) { return; } } } throw new AccessDeniedException("无权访问该资源!!!"); } public boolean supports(ConfigAttribute attribute) { return true; } public boolean supports(Class<?> clazz) { return true; } } 8.自定义 MySimpleUrlAuthenticationSuccessHandler.class public class MySimpleUrlAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { ...... } 9.自定义MySimpleUrlAuthenticationFailureHandler.class public class MySimpleUrlAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { ...... }
[code]参考博客:https://www.cnblogs.com/xiongyu/archive/2012/07/21/2602220.html
[code]如有不足请指正!
- shiro 权限控制框架 入门级实例(一)加密、登录验证
- 手工搭建基于ABP的框架(3) - 登录,权限控制与日志
- session和cookie是什么,登录,权限控制,不登录不让访问资源
- ASP.NET 框架 之HttpModule 例程:实现登录控制和权限控制
- 权限控制框架Spring Security 和Shiro 的总结
- Spring security实现登录验证+权限控制
- ASP.NET 框架 之HttpModule 例程:实现登录控制和权限控制
- Spring Security --- 权限控制安全框架入门简介
- spring boot系列--spring security (基于数据库)登录和权限控制
- Spring security 自定义登录与权限控制
- ASP.NET 框架 之HttpModule 例程:实现登录控制和权限控制
- spring security 采用角色控制访问权限
- 基于MVC4+EasyUI的Web开发框架形成之旅--权限控制
- angular JS 基于ionic框架 开发移动端项目 实现进入前台 判断用户权限 控制项目UI布局和tab的部门显示和隐藏
- spring security控制权限的几种方法
- AppBoxPro - 细粒度通用权限管理框架(可控制表格行内按钮)源码提供下载
- 在struts2中使用拦截器(Interceptor)控制登录和权限
- shiro-springmvc-mybatis登录认证 权限控制
- Spring Security-利用URL地址进行权限控制
- 基于角色的权限控制在springMVC框架中的实现