Maven+Spring security4.2.3的教程和源码
Maven+Spring security4.2.3的教程和源码
根据网上的教程,我们知道Spring security有四种方式,从简到深为:1、不用数据库,全部数据写在配置文件,这个也是官方文档里面的demo;2、使用数据库,根据spring security默认实现代码设计数据库,也就是说数据库已经固定了,这种方法不灵活,而且那个数据库设计得很简陋,实用性差;3、spring security和Acegi不同,它不能修改默认filter了,但支持插入filter,所以根据这个,我们可以插入自己的filter来灵活使用;4、暴力手段,修改源码,前面说的修改默认filter只是修改配置文件以替换filter而已,这种是直接改了里面的源码,但是这种不符合OO设计原则,而且不实际,不可用。
本文是根据第三中方式来实现安全控制的,大概流程如下网上是这样的我也觉得是这样的
现在先大概过一遍整个流程,用户登陆,会被AuthenticationProcessingFilter拦截,调用AuthenticationManager的实现,而且AuthenticationManager会调用ProviderManager来获取用户验证信息(不同的Provider调用的服务不同,因为这些信息可以是在数据库上,可以是在LDAP服务器上,可以是xml配置文件上等),如果验证通过后会将用户的权限信息封装一个User放到spring的全局缓存SecurityContextHolder中,以备后面访问资源时使用。访问资源(即授权管理),访问url时,会通过AbstractSecurityInterceptor拦截器拦截,其中会调用FilterInvocationSecurityMetadataSource的方法来获取被拦截url所需的全部权限,在调用授权管理器AccessDecisionManager,这个授权管理器会通过spring的全局缓存SecurityContextHolder获取用户的权限信息,还会获取被拦截的url和被拦截url所需的全部权限,然后根据所配的策略(有:一票决定,一票否定,少数服从多数等),如果权限足够,则返回,权限不够则报错并调用权限不足页面看完可能会蒙不打紧,源码里有些注释。
我觉的最最重要的spring—security.xml的配置,弄明白一些标签对于理解Spring security也是挺重要的,先看下目录结构
关闭的那些包都没用到可以不用管。
pom.xml中加入
<!--spring security-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>4.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>4.2.3.RELEASE</version>
</dependency>
其他该干嘛、就干嘛,另外也贴出spring—security.xml,和web.Xml
Web.xml如下:
<?xml version="1.0" encoding="UTF-8" ?>
<web-app>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-security.xml</param-value>
</context-param>
<!--配置spring listener-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 防止Spring内存溢出监听器 -->
<listener>
<listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>
</listener>
<!-- <!–解决POST乱码问题–>-->
<filter>
<filter-name>CharacterEncodingFilter</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>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- <!–springmvc前端控制器配置–>-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-application.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- 权限 Spring Security3.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的过滤器 –>-->
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.xml</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.css</url-pattern>
</servlet-mapping>
</web-app>
下面就是spring—security.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"
xmlns:context="http://www.springframework.org/schema/context"
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-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.AdesK.*" />
<!-- 开发环境可以放置 <debug /> 配置,会输出详细Security处理日志,正式环境建议屏蔽 <debug /> 配置
<debug />-->
<!--设置那些资源可以访问,那些需要权限才能访问-->
<http auto-config="true" use-expressions="true" >
<intercept-url pattern="/login" access="permitAll" />
<intercept-url pattern="/access_Denied" access="permitAll" />
<intercept-url pattern="/WEB-INF/**" access="ROLE_USER" />
<!--把相应的目录的资源设置权限-->
<intercept-url pattern="/WEB-INF/views/**" access="isAuthenticated()" />
<intercept-url pattern="/**" access="isAuthenticated()" />
<!--起始页,前提必须要permitall-->
<form-login
login-page="/login"
username-parameter="username"
password-parameter="password"
login-processing-url="/index"
authentication-failure-url="/access_Denied"
authentication-success-forward-url="/index"/>
<!--是否允许多用户用一个账号登陆-->
<session-management >
<concurrency-control max-sessions="1"
error-if-maximum-exceeded="true" />
</session-management>
<custom-filter ref="mySecurityFilter" before="FILTER_SECURITY_INTERCEPTOR"></custom-filter>
<csrf disabled="true"/>
</http>
<authentication-manager alias="MyAuthenticationManager">
<authentication-provider user-service-ref="myUserDetailService">
<!--如果用户的密码采用加密的话 <password-encoder hash="md5" /> -->
</authentication-provider>
</authentication-manager>
<!--可以用注解开发-->
<beans:bean id="mySecurityFilter" class="com.AdesK.security.MySecurityFilter">
<beans:property name="accessDecisionManager" ref="myAccessDecisionManager"/>
<beans:property name="authenticationManager" ref="MyAuthenticationManager"/>
</beans:bean>
<beans:bean id="myUserDetailService" class="com.AdesK.security.MyUserDetailService"/>
<beans:bean id="mySecurityMetadataSource" class="com.AdesK.security.MySecurityMetadataSource"/>
<!-- <beans:bean id="myAuthenticationFilter" class="com.AdesK.security.MyAuthenticationFilter"/>-->
<beans:bean id="myAccessDecisionManager" class="com.AdesK.security.MyAccessDecisionManager"/>
</beans:beans>
自定义的拦截器,要extends AbstractSecurityInterceptor implements Filter满足这种要求
MySecurityFilter.java
package com.AdesK.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.SecurityMetadataSource;
import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
import org.springframework.security.access.intercept.InterceptorStatusToken;
import org.springframework.security.web.FilterInvocation;
import javax.annotation.PostConstruct;
import javax.servlet.*;
import java.io.IOException;
/**
* @author AdesKng
* @version 1.0
* @TIME 2017/10/07-21:10
* @E-mail 109
*/
public class MySecurityFilter extends AbstractSecurityInterceptor implements Filter {
//spring-security.xml里的myFilter的属性securityMetadataSource对应,
//其他的两个组件,已经在AbstractSecurityInterceptor定义
@Autowired
private MySecurityMetadataSource mySecurityMetadataSource;
@PostConstruct
public void init(){
System.out.println(" --------------- MySecurityFilter init--------------- ");
/*super.setAuthenticationManager(myAuthenticationManager);
super.setAccessDecisionManager(myAccessDecisionManager);*/
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println(" --------------- doFilter--------------- ");
FilterInvocation fi = new FilterInvocation(servletRequest, servletResponse, filterChain);
invoke(fi);
}
public void invoke(FilterInvocation fi) throws IOException, ServletException {
//fi里面有一个被拦截的url
//里面调用MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法获取fi对应的所有权限
//再调用MyAccessDecisionManager的decide方法来校验用户的权限是否足够
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
//执行下一个拦截器
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
super.afterInvocation(token, null);
}
}
@Override
public void destroy() {
}
@Override
public Class<?> getSecureObjectClass() {
return FilterInvocation.class;
}
@Override
public SecurityMetadataSource obtainSecurityMetadataSource() {
return this.mySecurityMetadataSource;
}
}
首先,登陆后,每次访问资源都会被这个拦截器拦截,会执行doFilter这个方法,这个方法调用了invoke方法,其中fi断点显示是一个url,最重要的是beforeInvocation这个方法,它首先会调用MyInvocationSecurityMetadataSource类的getAttributes方法获取被拦截url所需的权限,在调用MyAccessDecisionManager类decide方法判断用户是否够权限。弄完这一切就会执行下一个拦截器。
下面MyAccessDecisionManager.java
package com.AdesK.security;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import javax.annotation.PostConstruct;
import java.util.*;
/**
* @author AdesKng
* @version 1.0
* @TIME 2017/10/07-21:19
* @E-mail 109
*/
/*<!--资源源数据定义,将所有的资源和权限对应关系建立起来,即定义某一资源可以被哪些角色访问 -->*/
public class MySecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
private static Map<String, Collection<ConfigAttribute>> resourceMap = null;
@PostConstruct
private void loadResourceDefine() {
System.out.println("--------------- MySecurityMetadataSource init--------------- ");
resourceMap = new HashMap<String, Collection<ConfigAttribute>>();
Collection<ConfigAttribute> atts = new ArrayList<ConfigAttribute>();
//应该从数据库读取权限
ConfigAttribute ca = new SecurityConfig("ROLE_USER");
atts.add(ca);
resourceMap.put("/index.jsp", atts);
Collection<ConfigAttribute> attsno =new ArrayList<ConfigAttribute>();
ConfigAttribute cano = new SecurityConfig("ROLE_NO");
attsno.add(cano);
resourceMap.put("/other.jsp", attsno);
}
//参数是要访问的url,返回这个url对于的所有权限(或角色)
@Override
public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
String requestUrl =((FilterInvocation)o).getRequestUrl();
if(resourceMap == null) {
loadResourceDefine();
}
if(requestUrl.indexOf("?")>-1){
requestUrl=requestUrl.substring(0,requestUrl.indexOf("?"));
}
System.out.println("--------------- "+requestUrl+" --------------- ");
return resourceMap.get(requestUrl);
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> aClass) {
return true;
}
}
还有几个类就不贴了很长,自己到后面的网址download,
这些代码也不完全是自己写的,博取了众家之长,加上自己的理解平凑出来的,可能你网上看到的第4版的写法和我的不同,那就对了,我是按照第3个版本写出来了,改了很多。源码地址附上
https://github.com/AdesKing/SSM-spring-security4.2.3
慢慢理解,所有东西都在项目的security包中,用点时间就可以了。