Spring Security调研记录【七】--核心模型与实现
来源:互联网 发布:cloverv3.3是什么软件 编辑:程序博客网 时间:2024/05/16 04:58
网上有很多关于Spring Security文章中,都认为Spring Security(相对于shiro)过于复杂,个人认为复杂的是Spring Security的官方文档而不是Spring Security本身。
Spring Security满足了用户认证与授权的几乎所有应用场景,在其核心模型下,扩展随心所欲!
一、认证与权限过程模型及Spring Security的处理过程
我们普遍的认证与授权过程如下图所示。
①是否已登录?:
在Spring Security中,接口AuthenticationTrustResolver有一个方法isAnonymous(Authentication),用于判断当前用户是否为匿名用户(匿名用户则为未登录用户)。
AuthenticationTrustResolver接口的实现类由ExceptionTranslationFilter过滤器调用。
ExceptionTranslationFilter在处理AccessDeniedException异常时,如果当前用户为匿名则调用“②登录入口”;否则调用“⑤无权访问处理”。
②登录入口:
Spring Security中,登录入口由接口AuthenticationEntryPoint提供,如果你不想提供一个登录界面,而是通过json返回一个特定字符串,指示客户端提供登录入口,只要提供一个的AuthenticationEntryPoint实现类组装到Spring Security的上下文即可,在第三部分会一个具体实现。
AuthenticationEntryPoint由ExceptionTranslationFilter调用,默认实现是提供一个登录页面。
③登录认证是否通过?:
Spring Security中,登录认证由过滤器UsernamePasswordAuthenticationFilter(AbstractAuthenticationProcessingFilter)提供。过滤器通过判断当前Url是否为/login(可配置),是则处理。
AbstractAuthenticationProcessingFilter委托AuthenticationManager进行用户登录认证,成功则调用AuthenticationSuccessHandler进行处理,否则调用AuthenticationFailureHandler进行处理。
AuthenticationSuccessHandler默认处理是只记录Session,结束当前过滤器处理,进行下一个过滤器处理。
AuthenticationFailureHandler默认处理是抛出AuthenticationException异常,由后面的ExceptionTranslationFilter统一处理。ExceptionTranslationFilter对于AuthenticationException异常,会调用AuthenticationEntryPoint进行处理。
如果希望登录成功或失败通过Json返回客户端,就可以重实现接口AuthenticationSuccessHandler和AuthenticationFailureHandler,组装到Spring Security的上下文。
具体情况请见第二部分。
④是否有权访问?:
Spring Security中,是否有权访问资源的检查是在FilterSecurityInterceptor过滤器中,FilterSecurityInterceptor委托AccessDecisionManager进行权限检查。
具体情况请见第二部分。
⑤无权访问处理:
如果无权访问,则抛出AccessDeniedException异常。AccessDeniedException由ExceptionTranslationFilter统一处理,ExceptionTranslationFilter会委托接口AccessDeniedHandler进行处理。
AccessDeniedHandler的默认实现会抛出403错误。如希望在无权访问某资源时,返回json信息而不是403错误,可通过重实现AccessDeniedHandler,并组装到Spring Security上下文中即可。
二、核心模型
核心模型总结为如下三张图,Filter、Authentication、Access
1、过滤器模式
Spring Security整体是通过管道(过滤器)模式实现功能。过滤器以如下顺序执行:
1)ChannelProcessingFilter
官方解释:“because it might need to redirect to a different protocol”
未明,暂且不理。
2)SecurityContextPersistenceFilter
本过滤器作用为从Session中加载SecurityContext (可获得Authentication),保存到SecurityContextHolder中,在处理完成后,把SecurityContextHolder中的SecurityContext 保存到session中。
如果要实现session集中化存储或缓存,则需要修改本过滤器。
3)ConcurrentSessionFilter
4)AbstractAuthenticationProcessingFilter
5)SecurityContextHolderAwareRequestFilter
6)JaasApiIntegrationFilter
7)RememberMeAuthenticationFilter
8)AnonymousAuthenticationFilter
9)ExceptionTranslationFilter
10)FilterSecurityInterceptor
2、登录模型
1)AbstractAuthenticationProcessingFilter
AbstractAuthenticationProcessingFilter只对特定的Url进行处理,如:/login(可配置),通过把userName和password信息封装到Authentication中,委托AuthenticationManager进行认证。如果认证成功则调用AuthenticationSuccessHandler进行处理,否则调用AuthenticationFailureHandler进行处理。
2)AuthenticationManager
AuthenticationManager里配置了一个AuthenticationProvider列表,循环调用其中的AuthenticationProvider进行认证,如果有一个认证成功则返回,如果所以AuthenticationProvider都认证失败则认为失败。
3)AuthenticationProvider
AuthenticationProvider默认实现,通过UserDetailsService加载UserDetails,对UserDetails与Authentication的credentials进行比较。
4)UserDetailsService
本接口只有一个方法:UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;如需要通过Spring Security与已有的用户管理系统对接,只要重实现该接口即可。
3、权限检测与异常转换处理模型
1)ExceptionTranslationFilter
Spring Security 在认证与权限检查过程中,都不立刻进行处理,而都是抛出相应的异常,由ExceptionTranslationFilter对不同异常调用不同接口进行处理。
AccessDeniedExceiption异常,对于非匿名用户,由AccessDeniedHandler进行处理,否则由AuthenticationEntryPoint进行处理。
AuthenticationException异常,则由AuthenticationEntryPoint进行处理。
AuthenticationTrustResolver接口用于判断当前用户是否为匿名用户。
2)FilterSecurityInterceptor
FilterSecurityInterceptor主要进行权限检查工作,如果需要再次认证,则也调用AuthenticationManager进行认证,但一般不用再次认证。
FilterSecurityInterceptor,通过FilterInvocationSecurityMetadataSource,获取当前Url资源需要的角色信息ConfigAttribute;同时把该ConfigAttribute和当前用户的Authentication,传递予AccessDecisionManager进行权限检查。
如果希望Url资源的角色要求可通过第三方系统获取,只要重实现FilterInvocationSecurityMetadataSource接口即可。
三、实例
实现通过username从第三方系统获取用户信息UserDetails(用户名、密码、是否超期、是否被锁、用户角色列表)
实现同第三方获取Url资源所需要的角色列表。
1、web.xml
<?xml version="1.0" encoding="UTF-8"?><web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns="http://java.sun.com/xml/ns/javaee" xmlns:jsp="http://java.sun.com/xml/ns/javaee/jsp"xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"id="WebApp_ID" version="3.0"><display-name>SpringSecurityTest</display-name><filter><filter-name>encodingFilter</filter-name><filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class><init-param><param-name>encoding</param-name><param-value>UTF-8</param-value></init-param></filter><filter-mapping><filter-name>encodingFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping><context-param><param-name>contextConfigLocation</param-name><param-value>/WEB-INF/spring-context.xml</param-value></context-param><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener> <filter><filter-name>springSecurityFilterChain</filter-name><filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class></filter><filter-mapping><filter-name>springSecurityFilterChain</filter-name><url-pattern>/*</url-pattern></filter-mapping> <servlet><servlet-name>springmvc</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><load-on-startup>1</load-on-startup></servlet><servlet-mapping><servlet-name>springmvc</servlet-name><url-pattern>/</url-pattern></servlet-mapping><welcome-file-list><welcome-file>index.jsp</welcome-file></welcome-file-list><error-page><error-code>500</error-code><location>/errorpage.jsp</location></error-page><error-page><error-code>400</error-code><location>/errorpage.jsp</location></error-page><error-page><error-code>404</error-code><location>/errorpage.jsp</location></error-page></web-app>
2、spring-security-context.xml配置
<?xml version="1.0" encoding="UTF-8"?><beans:beans xmlns="http://www.springframework.org/schema/security"xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd"><http pattern="/**/*.css" security="none" /><http pattern="/**/*.js" security="none" /><http pattern="/security/**" access-decision-manager-ref="accessDecisionManager" once-per-request="false"><form-login login-page="/security/login"login-processing-url="/security/loginprocess" default-target-url="/security/index"always-use-default-target="false" authentication-failure-url="/security/login?error=wrong_login_data"username-parameter="username" password-parameter="password" /><logout logout-url="/security/logout" /><intercept-url pattern="/security/login" access="permitAll()" /><intercept-url pattern="/security/logout" access="permitAll()" /><intercept-url pattern="/security/**" access="hasRole('NORMALUSER')" /> <custom-filter ref="winssageFilterSecurityInterceptor"before="FILTER_SECURITY_INTERCEPTOR" /> <csrf disabled="true" /></http><global-method-security pre-post-annotations="enabled" /><beans:bean name="bcryptEncoder"class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" /><authentication-manager alias="authenticationManager"><authentication-provider user-service-ref='myUserDetailsService'><password-encoder ref="bcryptEncoder" /></authentication-provider></authentication-manager><beans:bean id="myUserDetailsService"class="com.winssage.spring.security.userdetails.WinssageUserDetailsService"><beans:property name="bcryptPasswordEncoder" ref="bcryptEncoder" /></beans:bean><beans:bean id="winssageFilterSecurityInterceptor"class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor"><beans:property name="authenticationManager" ref="authenticationManager" /><beans:property name="accessDecisionManager" ref="accessDecisionManager" /><beans:property name="securityMetadataSource" ref="securityMetadataSource" /></beans:bean><beans:bean id="accessDecisionManager"class="org.springframework.security.access.vote.AffirmativeBased"><beans:constructor-arg name="decisionVoters"><beans:list><beans:bean class="org.springframework.security.access.vote.RoleVoter"><beans:property name="rolePrefix" value="ROLE_" /></beans:bean><beans:beanclass="org.springframework.security.access.vote.AuthenticatedVoter" /><beans:beanclass="org.springframework.security.web.access.expression.WebExpressionVoter" /></beans:list></beans:constructor-arg></beans:bean><beans:bean id="securityMetadataSource"class="com.winssage.spring.security.access.intercept.WinssageSecurityMetadataSource"><beans:constructor-arg ref="securityMetadataSourceAdapter" /></beans:bean><beans:bean id="securityMetadataSourceAdapter"class="com.winssage.spring.security.DefaultSecurityMetadataSourceAdapter"></beans:bean></beans:beans>
3、WinssageUserDetailsService
package com.winssage.spring.security.userdetails;import java.util.ArrayList;import java.util.Collection;import java.util.HashSet;import java.util.List;import java.util.Set;import javax.annotation.Resource;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.core.authority.SimpleGrantedAuthority;import org.springframework.security.core.userdetails.User;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.core.userdetails.UsernameNotFoundException;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;public class WinssageUserDetailsService implements UserDetailsService {BCryptPasswordEncoder bcryptPasswordEncoder;@Overridepublic UserDetails loadUserByUsername(String username)throws UsernameNotFoundException {List<GrantedAuthority> grantedAuths = new ArrayList<GrantedAuthority>();grantedAuths.add(new SimpleGrantedAuthority("ROLE_USER"));grantedAuths.add(new SimpleGrantedAuthority("ROLE_NORMALUSER"));boolean enables = true;boolean accountNonExpired = true;boolean credentialsNonExpired = true;boolean accountNonLocked = true;String password=(null==bcryptPasswordEncoder)?"123456":bcryptPasswordEncoder.encode("123456");User userdetail = new User(username, password, enables,accountNonExpired, credentialsNonExpired, accountNonLocked,grantedAuths);return userdetail;}public BCryptPasswordEncoder getBcryptPasswordEncoder() {return bcryptPasswordEncoder;}public void setBcryptPasswordEncoder(BCryptPasswordEncoder bcryptPasswordEncoder) {this.bcryptPasswordEncoder = bcryptPasswordEncoder;}}
4、WinssageSecurityMetadataSource
package com.winssage.spring.security.access.intercept;import java.util.ArrayList;import java.util.Collection;import java.util.HashSet;import java.util.LinkedHashMap;import java.util.Map;import java.util.Set;import javax.servlet.http.HttpServletRequest;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.springframework.expression.ExpressionParser;import org.springframework.expression.ParseException;import org.springframework.security.access.ConfigAttribute;import org.springframework.security.access.SecurityConfig;import org.springframework.security.access.expression.SecurityExpressionHandler;import org.springframework.security.web.FilterInvocation;import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource;import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;import org.springframework.security.web.util.matcher.RequestMatcher;import org.springframework.util.Assert;public final class WinssageSecurityMetadataSource implementsFilterInvocationSecurityMetadataSource {protected final Log logger = LogFactory.getLog(getClass());private SecurityMetadataSourceAdapter adapter = null;// ~ Constructors// ===================================================================================================public WinssageSecurityMetadataSource(SecurityMetadataSourceAdapter adapter) {this.adapter = adapter;}// ~ Methods// ========================================================================================================public Collection<ConfigAttribute> getAllConfigAttributes() {return null;}public Collection<ConfigAttribute> getAttributes(Object object) {Set<ConfigAttribute> resultAttributes = new HashSet<ConfigAttribute>();ConfigAttribute resultAttr;String url = ((FilterInvocation) object).getRequestUrl();Collection<AccessAttribute> accessAttributes = adapter.getAttributes(url);if(null==accessAttributes||accessAttributes.size()==0)return null;for (AccessAttribute accessAttribute : accessAttributes) {if (null == accessAttribute)continue;resultAttr = new SecurityConfig(accessAttribute.getAttribute());resultAttributes.add(resultAttr);} <span style="white-space:pre"></span>return resultAttributes;}public boolean supports(Class<?> clazz) {return FilterInvocation.class.isAssignableFrom(clazz);}}
5、DefaultSecurityMetadataSourceAdapter
package com.winssage.spring.security;import java.util.Collection;import java.util.HashSet;import org.springframework.security.access.ConfigAttribute;import com.winssage.spring.security.access.intercept.AccessAttribute;import com.winssage.spring.security.access.intercept.DefaultAccessAttribute;import com.winssage.spring.security.access.intercept.SecurityMetadataSourceAdapter;public class DefaultSecurityMetadataSourceAdapter implementsSecurityMetadataSourceAdapter {@Overridepublic Collection<AccessAttribute> getAttributes(Object object) {if(!(object instanceof String)) return null;Collection<AccessAttribute> attributes=new HashSet<AccessAttribute>();String url=(String)object;if (!url.equals("/security/index")) {return null;}AccessAttribute attr = new DefaultAccessAttribute("ROLE_ADMIN");attributes.add(attr);attr = new DefaultAccessAttribute("ROLE_USER");attributes.add(attr);return attributes;}}
- Spring Security调研记录【七】--核心模型与实现
- Spring Security调研记录【三】--实现Method Security
- Spring Security调研记录【一】--实现基本认证与Url权限控制
- Spring Security调研记录【二】--实现异步Json请求的基本认证与Url权限控制
- Spring Security调研记录【五】--基于Cas实现单点登录
- Spring Security教程(七)
- Spring Boot(七):Spring Security如何启用与禁用CSRF
- spring security 学习记录
- 【Spring Security实战系列】Spring Security实战(七)
- Spring Security——核心类简介
- Spring Security的核心拦截器
- 3. Spring Security 核心类简介
- spring security(三)核心类
- Spring Security-03-核心类简介
- spring-security核心类解析--整理中....
- Spring Boot与Spring Security
- Spring Security(Acegi)实现原理与应用一
- Spring Security(Acegi)实现原理与应用二
- arm-none-linux-gnueabi交叉工具链安装 ,介绍,区别总结
- iOS应用架构谈 view层的组织和调用方案
- Unknown MySQL server host 'localhost' (0)
- 如何使用Panel来实现一个可以从屏幕边缘拖出或拖进的控制面板
- jsonlib json,xml,object 互转
- Spring Security调研记录【七】--核心模型与实现
- C#实现回车实现快捷键功能键的功能
- Android学习之优化美女图片浏览器
- ACM-- n-1位数
- 微软正式提供Visual Studio 2013正式版下载(附直接链接汇总)
- C语言:全局变量在多个c文件中公用的方法
- jump game
- android:exported
- 设计之路 -- 如何进行软件需求分析?