接触Spring Security是因为在面试之前,面试官要我用Spring Boot+Spring Security实现用户登录校验的功能。在此之前接触过一些Spring Boot,对Spring Security则完全没有了解,只知道它是一个权限管理的框架。也好,借此机会写些文章记录下了解和使用Spring Security的过程。
Spring Security是一套验证授权框架。Spring Security最重要的两个功能也就是验证(authentication)和授权(authorization)。搭建网站过程中以下需求十分常见:
- 网站分为首页、登录页、用户页面、管理员页面和报错页面;
- 首页和登录页访问无需权限;
- 进入用户页面需要USER权限,进入管理员页面需要ADMIN权限;
- 登录时需要用户名加密码,用户名或密码有错误时需要报错;
- 若用户未登录想直接访问首页或登录页以外的页面,则需要跳转登录页面进行登录。
不用Spring Security框架,常见的做法是先利用Filter拦截需要权限的页面,通过Session判断用户是否登录且拥有对应权限,若已登录且有权限就进行过滤,未登录或者没有权限则拦截、跳转登录页面,提示相关信息。我们知道要访问某个页面或者进行某个操作时,需要满足已登录和有权限两个条件,这就是Spring Security框架核心的两个功能:验证和授权。
验证(authentication):指的是建立系统使用者信息( principal )的过程,使用者可以是一个设备、用户等;
授权(authorization):指的是判断某个 principal 在我们的应用是否允许执行某个操作。在进行授权判断之前,要求所要使用到的规则必须在验证过程中已经建立好。
一个标准的验证场景:
- 一个用户被提示使用用户名和密码登录;
- 系统成功的验证了用户名与密码是匹配的;
- 获取用户的上下文信息;
- 建立这个用户的安全上下文(security context );
- 当用户进行受访问控制机制保护的操作时,访问控制机制会依据当前安全上下文信息检查这个操作所需的权限。
接下来说说,Spring Security是如何实现这些流程的,主要是流程,不怎么涉及源码,源码太多看了我也晕啊!
首先是容器启动时通过SecurityMetadataSource (自定义拦截器中的属性)中的loadResourceDefine方法加载系统资源与权限列表resourceMap(一个Map结构,资源[url]为key,权限[auth]为value )。
其次是WEB服务器启动,加载security内置的拦截器链:
- SecurityContextPersistenceFilter
- WebAsyncManagerIntegrationFilter
- HeaderWriterFilter
- CsrfFilter
- LogoutFilter
- UsernamePasswordAuthenticationFilter
- DefaultLoginPageGeneratingFilter
- BasicAuthenticationFilter
- RequestCacheAwareFilter
- SecurityContextHolderAwareRequestFilter
- AnonymousAuthenticationFilter
- SessionManagementFilter
- ExceptionTranslationFilter
- FilterSecurityInterceptor
当然,我们需要自定义一个拦截器配置具体的要拦截信息,并指定它在拦截器链中生效的位置。这就是Spring Security中四个重要的类:
AbstractSecurityInterceptor :继承此类定义一个拦截器,这个拦截器会加载在FILTER_SECURITY_INTERCEPTOR之前。
AuthenticationManager:读取登录用户信息、权限。
SecurityMetadataSource :加载资源与权限的全部对应关系的,并提供一个通过资源获取所有权限的方法。
AccessDecisionManager:授权器,通过登录用户的权限信息、资源、获取资源所需的权限来根据不同的授权策略来判断用户是否有权限访问资源。
在网上找的一个自定义拦截器的例子:
/** * 该拦截器用以添加资源拦截 * * 添加一个拦截器,配置在 FILTER_SECURITY_INTERCEPTOR之前 * 继承本来最后的 AbstractSecurityInterceptor以实现 登录过程 * * * 过滤器依赖于 SecurityContext 和 Authentication 对象,来进行辨别用户的 GrantedAuthority。 * 所以,我们要将这个过滤器的位置放在 FilterSecurityInterceptor 之前 * */ public class AppFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter { private FilterInvocationSecurityMetadataSource securityMetadataSource; //拦截器入口 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { FilterInvocation fi = new FilterInvocation(request, response, chain); invoke(fi); } /** * fi里面有一个被拦截的url * 调用MyInvocationSecurityMetadataSource的getAttributes(Object object) * 这个方法获取fi对应的所有权限 * 再调用MyAccessDecisionManager的decide方法来校验用户的权限是否足够 */ public void invoke(FilterInvocation fi) throws IOException, ServletException { InterceptorStatusToken token = super.beforeInvocation(fi); try { // 执行下一个拦截器 fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } finally { super.afterInvocation(token, null); } } //实现接口的方法 public Class<? extends Object> getSecureObjectClass() { return FilterInvocation.class; } public SecurityMetadataSource obtainSecurityMetadataSource() { return this.securityMetadataSource; } public void destroy() { } public void init(FilterConfig arg0) throws ServletException { } //用以注入securityMetadataSource public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() { return this.securityMetadataSource; } public void setSecurityMetadataSource( FilterInvocationSecurityMetadataSource newSource) { this.securityMetadataSource = newSource; }
定义好拦截器之后,在xml文件中进行配置:
1.配置spring security的配置文件securityConfig.xml
<!--忽略http的其他配置,此处只用于说明如何配置Filter-> <http auto-config="true" use-expressions="true" > <custom-filter ref="appFilter" before="FILTER_SECURITY_INTERCEPTOR" /> </http> <!--一个自定义的filter,必须包含 authenticationManager,accessDecisionManager,securityMetadataSource三个属性--> <beans:bean id="appFilter" class="security.filter.AppFilterSecurityInterceptor"> <beans:property name="authenticationManager" ref="appAuthenticationManager" /> <beans:property name="accessDecisionManager" ref="appAccessDecisionManagerBean" /> <beans:property name="securityMetadataSource" ref="appSecurityMetadataSource" /> </beans:bean><!--在java文件中对authenticationManager,accessDecisionManager,securityMetadataSource接口进行扩展,在xml文件中进行配置--><!--资源源数据定义,将所有的资源和权限对应关系建立起来,即定义某一资源可以被哪些角色访问-->
<beans:bean id="appSecurityMetadataSource" class="security.filter.properties.AppInvocationSecurityMetadataSource" > <beans:constructor-arg name="resourcesDao" ref="securityResourcesDao"/> </beans:bean> <!--访问决策器,决定某个用户具有的角色,是否有足够的权限去访问某个资源 --> <beans:bean id="appAccessDecisionManagerBean" class="security.filter.properties.AppAccessDecisionManager"> </beans:bean> <!--在这个类中,你就可以从数据库中读入用户的密码,角色信息,是否锁定,账号是否过期等 --> <beans:bean id="appUserDetailService" class="security.service.impl.AppUserDetailService" />
2.web.xml文件中的配置
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <!--加载Spring XML配置文件 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value> classpath:securityConfig.xml </param-value> </context-param> <!-- Spring Secutiry3.1的过滤器链配置 --> <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> <!-- Spring 容器启动监听器 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!--系统欢迎页面 --> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>
这是Spring Security与Spring的集成,与Spring Boot的集成当然更简便些,后面会写。
原文地址:https://www.cnblogs.com/linyukun/p/9862720.html