网上有非常多关于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. python全套视频十五期(116G)

    python全套视频,第十五期,从入门到精通,基础班,就业班,面试,软件包 所属网站分类: 资源下载 > python视频教程 作者:精灵 链接:http://www.pythonheidong ...

  2. Spring MVC 接入 rabbitMQ

    依赖包 <dependency> <groupId>org.springframework.amqp</groupId> <artifactId>spr ...

  3. LeetCode(66)Plus One

    题目 Given a non-negative number represented as an array of digits, plus one to the number. The digits ...

  4. angular2 启动步骤

    以下内容转自网络 1. 创建项目文件夹 创建一个新的文件夹来保存你的项目,比如一开始有个self就好了 2.安装基础库 首先确保已经安装了node.js 我们使用 npm package manage ...

  5. JavaScript高级程序设计中第2、3章中细节知识点

    第2章 1.<script src='script.js' type='text/javascript'></script>中,只要不包含defer和async属性,浏览器都会 ...

  6. luogu3313 [SDOI2014]旅行

    对每一个宗教建一棵线段树,然后树剖搞搞 #include <iostream> #include <cstdio> using namespace std; int n, m, ...

  7. HDU 1078 dfs+dp

    题目大意: 在n*n的矩阵中,每个格子放置了一定数量的食物,一只老鼠每次水平或竖直最多移动k格,每次到的位置食物都要比前一次多,问最后一共能得到多少食物 这道题利用记忆化搜索做,利用nowstate不 ...

  8. 汕头市赛srm8 C-3

    n<=100000个点m<=300000条边有权无向联通图,给出K<=10000个特殊点求K个点中任意两点最短路的最小值. 方法一:K小,随便搞.先构造最短路树,在最短路树上Dijk ...

  9. solr相关文章

    Solr集群架构概述及delta-import详细配置 背景 由于项目原因,重新熟悉了下Solr,版本为3.6,搭建了主从Solr服务,并使用DIH从RDBMS数据源增量更新索引. 其实也没什么技术含 ...

  10. delphi操作xml学习笔记 之一 入门必读

    Delphi 对XML的支持---TXMLDocument类       Delphi7 支持对XML文档的操作,可以通过TXMLDocument类来实现对XML文档的读写.可以利用TXMLDocum ...