网上有非常多关于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的上下文就可以,在第三部分会一个详细实现。

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

③登录认证是否通过?:

Spring Security中,登录认证由过滤器UsernamePasswordAuthenticationFilter(AbstractAuthenticationProcessingFilter)提供。过滤器通过推断当前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、登录模型

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbGxkMjAwMg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">

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; } }

Spring Security调研记录【七】--核心模型与实现的更多相关文章

  1. spring security实现记录用户登录时间等信息

    目录 spring security实现记录用户登录时间等信息 一.原理分析 二.实现方式 2.1 自定义AuthenticationSuccessHandler实现类 2.2 在spring-sec ...

  2. Spring Security(03)——核心类简介

    目录 1.1     Authentication 1.2     SecurityContextHolder 1.3     AuthenticationManager和Authentication ...

  3. Spring Security 学习记录

    一.核心拦截器详细说明 1.WebAsyncManagerIntegrationFilter 根据请求封装获取WebAsyncManager 从WebAsyncManager获取/注册Security ...

  4. 【Spring】关于Boot应用中集成Spring Security你必须了解的那些事

    Spring Security Spring Security是Spring社区的一个顶级项目,也是Spring Boot官方推荐使用的Security框架.除了常规的Authentication和A ...

  5. Spring Boot中集成Spring Security 专题

    check to see if spring security is applied that the appropriate resources are permitted: @Configurat ...

  6. Java Web系列:Spring Security 基础

    Spring Security虽然比JAAS进步很大,但还是先天不足,达不到ASP.NET中的认证和授权的方便快捷.这里演示登录.注销.记住我的常规功能,认证上自定义提供程序避免对数据库的依赖,授权上 ...

  7. Spring Security(一) —— Architecture Overview

    摘要: 原创出处 https://www.cnkirito.moe/spring-security-1/ 「老徐」欢迎转载,保留摘要,谢谢! 1 核心组件 一直以来我都想写一写Spring Secur ...

  8. SPRING SECURITY JAVA配置:Web Security

    在前一篇,我已经介绍了Spring Security Java配置,也概括的介绍了一下这个项目方方面面.在这篇文章中,我们来看一看一个简单的基于web security配置的例子.之后我们再来作更多的 ...

  9. Spring Security(09)——Filter

    目录 1.1     Filter顺序 1.2     添加Filter到FilterChain 1.3     DelegatingFilterProxy 1.4     FilterChainPr ...

随机推荐

  1. 一个关于vue+mysql+express的全栈项目(六)------ 聊天模型的设计

    一.数据模型的设计 这里我们先不讨论群聊的模型,指讨论两个人之间的聊天,我们可以把两个人实时聊天抽象为(点对点)的实时通讯,如下图 我们上面的所说的模型其实也就是数据包的模型应该怎么设计,换句话说就是 ...

  2. Sublime Text3 解决中文乱码 & 可用注册码 & 设置默认打开方式

    Sublime Text3注册码 Sublime Text Build 3065 License key 复制如下三个任意一个正版注册码即可 -– BEGIN LICENSE -– Andrew We ...

  3. Tomcat启动慢(运行shutdown.sh的时候报错)

    Using CATALINA_BASE: /usr/local/tomcatUsing CATALINA_HOME: /usr/local/tomcatUsing CATALINA_TMPDIR: / ...

  4. Android记录2013年10月20日

    1. ailed to fectch URl https://dl-ssl.google.com/android/repository/addons_list.xml, reason: Connect ...

  5. webservice学习第一天

    Webservice Webservice就是一种远程调用技术,他的作用就是从远程系统中获取业务数据 1 课程安排 l 什么是webservice l Webservice入门程序 l Webserv ...

  6. Leetcode 300.最长上升子序列

    最长上升子序列 给定一个无序的整数数组,找到其中最长上升子序列的长度. 示例: 输入: [10,9,2,5,3,7,101,18] 输出: 4 解释: 最长的上升子序列是 [2,3,7,101],它的 ...

  7. HDU 4597

    题目大意: 两人轮流从两堆牌从抽取最顶端或者最底部的牌,得到的分数加到自己身上,问先拿牌的最多能得多少分 记忆化搜索,2堆牌的底和顶,有四种方法,根据四种方法来找到最优解 #include <c ...

  8. Poj3253:Fence Repair 【贪心 堆】

    题目大意:背景大概是个资本家剥削工人剩余价值的故事....有一块木板,要把它切成几个长度,切一次的费用是这整块被切木板的长度,例如将一个长度为21的木板切成2和19两块费用为21,切成两块的长度及顺序 ...

  9. 洛谷P1504 积木城堡

    题目描述 XC的儿子小XC最喜欢玩的游戏用积木垒漂亮的城堡.城堡是用一些立方体的积木垒成的,城堡的每一层是一块积木.小XC是一个比他爸爸XC还聪明的孩子,他发现垒城堡的时候,如果下面的积木比上面的积木 ...

  10. hdu 5195 DZY Loves Topological Sorting BestCoder Round #35 1002 [ 拓扑排序 + 优先队列 || 线段树 ]

    传送门 DZY Loves Topological Sorting Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 131072/131 ...