原文地址 http://blog.csdn.net/u012367513/article/details/38866465
spring security 是现在比较流行的安全框架了,可以很容易的集成到项目中实现认证与授权的管理。本文基于spring security 3.1.3版本,主要参考了
L-二当家的的分享
LocalFilterSecurityInterceptor.java 登陆后,每次访问资源都会被这个拦截器拦截,会执行doFilter这个方法
package security;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
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 org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
public class LocalFilterSecurityInterceptor 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);
}
public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
return this.securityMetadataSource;
}
public Class<? extends Object> getSecureObjectClass() {
return FilterInvocation.class;
}
public void invoke(FilterInvocation fi) throws IOException, ServletException {
//fi里面有一个被拦截的url
//里面调用LocalInvocationSecurityMetadataSource的getAttributes(Object object)这个方法获取fi对应的所有权限
//再调用LocalAccessDecisionManager的decide方法来校验用户的权限是否足够
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
//执行下一个拦截器
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
super.afterInvocation(token, null);
}
}
public SecurityMetadataSource obtainSecurityMetadataSource() {
return this.securityMetadataSource;
}
public void setSecurityMetadataSource(FilterInvocationSecurityMetadataSource newSource) {
this.securityMetadataSource = newSource;
}
public void destroy() {
}
public void init(FilterConfig arg0) throws ServletException {
}
}
LocalInvocationSecurityMetadataSource
这个用来加载资源与权限的全部对应关系的,并提供一个通过资源获取所有权限的方法
package security;
import java.util.*;
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 util.AntUrlPathMatcher;
import util.UrlMatcher;
public class LocalInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
private UrlMatcher urlMatcher = new AntUrlPathMatcher();
private static Map<String, Collection<ConfigAttribute>> resourceMap = null;
//tomcat启动时实例化一次
public LocalInvocationSecurityMetadataSource() {
loadResourceDefine();
}
//tomcat开启时加载一次,加载所有url和权限(或角色)的对应关系
private void loadResourceDefine() {
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);
Collection<ConfigAttribute> attsadmin =new ArrayList<ConfigAttribute>();
ConfigAttribute canoadmin = new SecurityConfig("ROLE_ADMIN");
attsadmin.add(canoadmin);
resourceMap.put("/admin.jsp", attsadmin);
}
//参数是要访问的url,返回这个url对于的所有权限(或角色)
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
Collection<ConfigAttribute> configAttributes = new ArrayList<ConfigAttribute>();
// 将参数转为url
String url = ((FilterInvocation)object).getRequestUrl();
Iterator<String>ite = resourceMap.keySet().iterator();
while (ite.hasNext()) {
String resURL = ite.next();
if (urlMatcher.pathMatchesUrl(resURL, url)) {
configAttributes.addAll(resourceMap.get(resURL));
}
}
return configAttributes.isEmpty() ? null : configAttributes;
}
public boolean supports(Class<?>clazz) {
return true;
}
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
}
LocalUserDetailService.java 提供通过用户名获取用户所有(包含权限)信息的接口
package security;
import org.springframework.dao.DataAccessException;
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 java.util.ArrayList;
import java.util.Collection;
public class LocalUserDetailService implements UserDetailsService {
//登陆验证时,通过username获取用户的所有权限信息,
//并返回User放到spring的全局缓存SecurityContextHolder中,以供授权器使用
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException {
Collection<GrantedAuthority> auths = new ArrayList<GrantedAuthority>();
SimpleGrantedAuthority auth1=new SimpleGrantedAuthority("ROLE_ADMIN");
SimpleGrantedAuthority auth2=new SimpleGrantedAuthority("ROLE_USER");
SimpleGrantedAuthority auth3=new SimpleGrantedAuthority("ROLE_NO");
//用户名是admin是天啊及所有权限
if(username.equals("admin")){
auths.add(auth1);
auths.add(auth2);
auths.add(auth3);
}else {
//其他情况去掉admin权限
auths.add(auth2);
auths.add(auth3);
}
//这里写死密码为admin,正常情况应该从数据库中查询
User user = new User(username, "admin", true, true, true, true, auths);
return user;
}
}
LocalAccessDecisionManager.java 判断用户是否具有访问资源的权限
package security;
import java.util.Collection;
import java.util.Iterator;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
public class LocalAccessDecisionManager implements AccessDecisionManager {
//检查用户是否够权限访问资源
//参数authentication是从spring的全局缓存SecurityContextHolder中拿到的,里面是用户的权限信息
//参数object是url
//参数configAttributes所需的权限
public void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes)
throws AccessDeniedException, InsufficientAuthenticationException {
if (configAttributes == null) {
return;
}
Iterator<ConfigAttribute> ite = configAttributes.iterator();
while (ite.hasNext()) {
ConfigAttribute ca = ite.next();
String needRole = ca.getAttribute();
for (GrantedAuthority ga : authentication.getAuthorities()) {
if (needRole.equals(ga.getAuthority())) {
return;
}
}
}
//注意:执行这里,后台是会抛异常的,但是界面会跳转到所配的access-denied-page页面
throw new AccessDeniedException("no right");
}
public boolean supports(ConfigAttribute attribute) {
return true;
}
public boolean supports(Class<?> clazz) {
return true;
}
}
AntUrlPathMatcher.java 匹配url工具类
package util;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
public class AntUrlPathMatcher implements UrlMatcher {
private boolean requiresLowerCaseUrl;
private PathMatcher pathMatcher;
public AntUrlPathMatcher() {
this(true);
}
public AntUrlPathMatcher(boolean requiresLowerCaseUrl)
{
this.requiresLowerCaseUrl = true;
this.pathMatcher = new AntPathMatcher();
this.requiresLowerCaseUrl = requiresLowerCaseUrl;
}
public Object compile(String path) {
if (this.requiresLowerCaseUrl) {
return path.toLowerCase();
}
return path;
}
public void setRequiresLowerCaseUrl(boolean requiresLowerCaseUrl){
this.requiresLowerCaseUrl = requiresLowerCaseUrl;
}
public boolean pathMatchesUrl(Object path, String url) {
if (("/**".equals(path)) || ("**".equals(path))) {
return true;
}
return this.pathMatcher.match((String)path, url);
}
public String getUniversalMatchPattern() {
return"/**";
}
public boolean requiresLowerCaseUrl() {
return this.requiresLowerCaseUrl;
}
public String toString() {
return super.getClass().getName() + "[requiresLowerCase='"
+ this.requiresLowerCaseUrl + "']";
}
}
UrlMatcher.java 工具接口
package util;
public interface UrlMatcher{
Object compile(String paramString);
boolean pathMatchesUrl(Object paramObject, String paramString);
String getUniversalMatchPattern();
boolean requiresLowerCaseUrl();
}
securityConfig.xml
spring-security 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<b:beans xmlns="http://www.springframework.org/schema/security"
xmlns:b="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-3.1.xsd">
<!--登录页面不过滤 -->
<http pattern="/login.jsp" security="none" />
<http access-denied-page="/accessDenied.jsp">
<form-login login-page="/login.jsp" />
<!--访问/admin.jsp资源的用户必须具有ROLE_ADMIN的权限 -->
<!-- <intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" /> -->
<!--访问/**资源的用户必须具有ROLE_USER的权限 -->
<intercept-url pattern="/**" access="ROLE_USER" />
<session-management>
<concurrency-control max-sessions="1"
error-if-maximum-exceeded="false" />
</session-management>
<!--增加一个filter,这点与 Acegi是不一样的,不能修改默认的filter了, 这个filter位于FILTER_SECURITY_INTERCEPTOR之前 -->
<custom-filter ref="myFilter" before="FILTER_SECURITY_INTERCEPTOR" />
</http>
<!--一个自定义的filter,必须包含 authenticationManager,accessDecisionManager,securityMetadataSource三个属性,
我们的所有控制将在这三个类中实现,解释详见具体配置 -->
<b:bean id="myFilter"
class="security.LocalFilterSecurityInterceptor">
<b:property name="authenticationManager" ref="authenticationManager" />
<b:property name="accessDecisionManager" ref="myAccessDecisionManagerBean" />
<b:property name="securityMetadataSource" ref="securityMetadataSource" />
</b:bean>
<!--验证配置,认证管理器,实现用户认证的入口,主要实现UserDetailsService接口即可 -->
<authentication-manager alias="authenticationManager">
<authentication-provider user-service-ref="myUserDetailService">
<!--如果用户的密码采用加密的话 <password-encoder hash="md5" /> -->
</authentication-provider>
</authentication-manager>
<!--在这个类中,你就可以从数据库中读入用户的密码,角色信息,是否锁定,账号是否过期等 -->
<b:bean id="myUserDetailService" class="security.LocalUserDetailService" />
<!--访问决策器,决定某个用户具有的角色,是否有足够的权限去访问某个资源 -->
<b:bean id="myAccessDecisionManagerBean"
class="security.LocalAccessDecisionManager">
</b:bean>
<!--资源源数据定义,将所有的资源和权限对应关系建立起来,即定义某一资源可以被哪些角色访问 -->
<b:bean id="securityMetadataSource"
class="security.LocalInvocationSecurityMetadataSource" />
</b:beans>
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.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>
accessDenied.jsp 权限不足页面
<%@page pageEncoding="utf-8"%>
<!DOCTYPEHTMLPUBLIC"-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>My JSP 'accessDenied.jsp' starting page</title>
</head>
<body>
accessDenied!!!
</body>
</html>
admin.jsp 管理员页面
<%@page pageEncoding="utf-8"%>
<!DOCTYPEHTMLPUBLIC"-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>My JSP 'admin.jsp' starting page</title>
</head>
<body>
欢迎来到管理员页面.
</body>
</html>
index.jsp
主页
<%@page pageEncoding="UTF-8"%>
<!DOCTYPEHTMLPUBLIC"-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>My JSP 'index.jsp' starting page</title>
</head>
<body>
<h3>这是首页</h3>欢迎
<a href="admin.jsp">进入admin页面</a>
<a href="other.jsp">进入其它页面</a>
</body>
</html>
login.jsp
<%@page pageEncoding="UTF-8"%>
<!DOCTYPEhtmlPUBLIC"-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>登录</title>
</head>
<body>
<form action ="j_spring_security_check" method="POST">
<table>
<tr>
<td>用户:</td>
<td><input type ='text' name='j_username'></td>
</tr>
<tr>
<td>密码:</td>
<td><input type ='password' name='j_password'></td>
</tr>
<tr>
<td><input name ="reset" type="reset"></td>
<td><input name ="submit" type="submit"></td>
</tr>
</table>
</form>
</body>
</html>
other.jsp
<%@ page pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>My JSP 'other.jsp' starting page</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
</head>
<body>
<h3>这里是Other页面</h3>
</body>
</html>
pom 依赖
<properties>
<spring-security.version>3.1.3.RELEASE</spring-security.version>
<spring.version>4.3.10.RELEASE</spring.version>
<servlet-api.version>3.0-alpha-1</servlet-api.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>${spring-security.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
<version>${spring-security.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>${spring-security.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>${servlet-api.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>