• Spring Security调研记录【七】--核心模型与实现


    网上有非常多关于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返回一个特定字符串,指示client提供登录入口。仅仅要提供一个的AuthenticationEntryPoint实现类组装到Spring Security的上下文就可以,在第三部分会一个详细实现。

    AuthenticationEntryPointExceptionTranslationFilter调用。默认实现是提供一个登录页面。


             ③登录认证是否通过?:

    Spring Security中,登录认证由过滤器UsernamePasswordAuthenticationFilterAbstractAuthenticationProcessingFilter)提供。过滤器通过推断当前Url是否为/login(可配置)。是则处理。

    AbstractAuthenticationProcessingFilter托付AuthenticationManager进行用户登录认证,成功则调用AuthenticationSuccessHandler进行处理。否则调用AuthenticationFailureHandler进行处理

    AuthenticationSuccessHandler默认处理是仅仅记录Session,结束当前过滤器处理,进行下一个过滤器处理。

    AuthenticationFailureHandler默认处理是抛出AuthenticationException异常,由后面的ExceptionTranslationFilter统一处理。ExceptionTranslationFilter对于AuthenticationException异常,会调用AuthenticationEntryPoint进行处理

    假设希望登录成功或失败通过Json返回client,就能够重实现接口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(用户名、password、是否超期、是否被锁、用户角色列表)

    实现同第三方获取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:bean class="org.springframework.security.access.vote.AuthenticatedVoter" /> <beans:bean class="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;
    	
    	@Override
    	public 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 implements
    		FilterInvocationSecurityMetadataSource {
    	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 implements
    		SecurityMetadataSourceAdapter {
    
    	@Override
    	public 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;
    
    	}
    
    }
    


  • 相关阅读:
    PHP之旅3 php数组以及遍历数组 以及each() list() foreach()
    NSSetUncaughtExceptionHandler
    runtime
    Objective-C中的instancetype和id区别
    tableView 局部刷新
    CGAffineTransform
    iOS中文本属性Attributes
    ios 相机 自定义 相片的截取
    php程序的生命周期
    PHP代码执行流程
  • 原文地址:https://www.cnblogs.com/jzssuanfa/p/7224181.html
Copyright © 2020-2023  润新知