spring security使用分类:
本文面向读者:
spring security的简单原理:
spring security使用实现(基于spring security3.1.4):
<?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>
上面那个配置不用多说了吧
<?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="com.erdangjiade.spring.security.MyFilterSecurityInterceptor">
<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="com.erdangjiade.spring.security.MyUserDetailService" />
<!--访问决策器,决定某个用户具有的角色,是否有足够的权限去访问某个资源 -->
<b:bean id="myAccessDecisionManagerBean"
class="com.erdangjiade.spring.security.MyAccessDecisionManager">
</b:bean>
<!--资源源数据定义,将所有的资源和权限对应关系建立起来,即定义某一资源可以被哪些角色访问 -->
<b:bean id="securityMetadataSource"
class="com.erdangjiade.spring.security.MyInvocationSecurityMetadataSource" /> </b:beans>
其实所有配置都在<http></http>里面,首先这个版本的spring security不支持了filter=none的配置了,改成了独立的<http pattern="/login.jsp" security="none"/>,里面你可以配登陆页面、权限不足的返回页面、注销页面等,上面那些配置,我注销了一些资源和权限的对应关系,笔者这里不需要在这配死它,可以自己写拦截器来获得资源与权限的对应关系。
了,而且实际上的登陆验证也是AuthenticationProcessingFilter拦截器调用authenticationManager来处理的,我们这个拦截器只是为了拿到验证用户信息而已(这里不太清楚,因为authenticationManager笔者设了断点,用户登陆后再也没调用这个类了,而且调用这个类时不是笔者自己写的那个拦截器调用的,看了spring技术内幕这本书才知道是AuthenticationProcessingFilter拦截器调用的)。
MyUserDetailService:
package com.erdangjiade.spring.security; import java.util.ArrayList;
import java.util.Collection; import org.springframework.dao.DataAccessException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.GrantedAuthorityImpl;
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; public class MyUserDetailService implements UserDetailsService { //登陆验证时,通过username获取用户的所有权限信息,
//并返回User放到spring的全局缓存SecurityContextHolder中,以供授权器使用
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException {
Collection<GrantedAuthority> auths=new ArrayList<GrantedAuthority>(); GrantedAuthorityImpl auth2=new GrantedAuthorityImpl("ROLE_ADMIN");
GrantedAuthorityImpl auth1=new GrantedAuthorityImpl("ROLE_USER"); if(username.equals("lcy")){
auths=new ArrayList<GrantedAuthority>();
auths.add(auth1);
auths.add(auth2);
} User user = new User(username, "lcy", true, true, true, true, auths);
return user;
}
}
其中UserDetailsService接口是spring提供的,必须实现的。别看这个类只有一个方法,而且这么简单,其中内涵玄机。
package com.erdangjiade.spring.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 MyFilterSecurityInterceptor 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
//里面调用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);
}
}
public SecurityMetadataSource obtainSecurityMetadataSource() {
return this.securityMetadataSource;
}
public void setSecurityMetadataSource(
FilterInvocationSecurityMetadataSource newSource)
{
this.securityMetadataSource = newSource;
}
public void destroy() { }
public void init(FilterConfig arg0) throws ServletException { }
}
继承AbstractSecurityInterceptor、实现Filter是必须的。
package com.erdangjiade.spring.security; import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map; 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 com.erdangjiade.spring.security.tool.AntUrlPathMatcher;
import com.erdangjiade.spring.security.tool.UrlMatcher; public class MyInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
private UrlMatcher urlMatcher = new AntUrlPathMatcher();
private static Map<String, Collection<ConfigAttribute>> resourceMap = null; //tomcat启动时实例化一次
public MyInvocationSecurityMetadataSource() {
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);
} //参数是要访问的url,返回这个url对于的所有权限(或角色)
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
// 将参数转为url
String url = ((FilterInvocation)object).getRequestUrl();
Iterator<String>ite = resourceMap.keySet().iterator();
while (ite.hasNext()) {
String resURL = ite.next();
if (urlMatcher.pathMatchesUrl(resURL, url)) {
return resourceMap.get(resURL);
}
}
return null;
}
public boolean supports(Class<?>clazz) {
return true;
}
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
}
实现FilterInvocationSecurityMetadataSource接口也是必须的。
package com.erdangjiade.spring.security.tool; public interface UrlMatcher{
Object compile(String paramString);
boolean pathMatchesUrl(Object paramObject, String paramString);
String getUniversalMatchPattern();
boolean requiresLowerCaseUrl();
}
package com.erdangjiade.spring.security.tool;
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 + "']";
}
}
然后MyAccessDecisionManager类的实现:
package com.erdangjiade.spring.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.access.SecurityConfig;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority; public class MyAccessDecisionManager 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=((SecurityConfig)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;
}
}
接口AccessDecisionManager也是必须实现的。
剩下的页面代码
<%@page language="java" import="java.util.*" 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>
index.jsp:
<%@page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@taglib prefix="sec" uri="http://www.springframework.org/security/tags"%>
<!DOCTYPEHTMLPUBLIC"-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>My JSP 'index.jsp' starting page</title>
</head> <body>
<h3>这是首页</h3>欢迎
<sec:authentication property ="name"/> ! <br>
<a href="admin.jsp">进入admin页面</a>
<a href="other.jsp">进入其它页面</a>
</body> </html>
admin.jsp:
<%@page language="java" import="java.util.*" pageEncoding="utf-8"%>
<!DOCTYPEHTMLPUBLIC"-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>My JSP 'admin.jsp' starting page</title>
</head>
<body>
欢迎来到管理员页面.
<br>
</body>
</html>
accessDenied.jsp:
<%@page language="java" import="java.util.*" pageEncoding="utf-8"%>
<!DOCTYPEHTMLPUBLIC"-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>My JSP 'admin.jsp' starting page</title>
</head>
<body>
欢迎来到管理员页面.
<br>
</body>
</html>
other.jsp:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>"> <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">
<!--
<link rel="stylesheet" type="text/css" href="styles.css">
--> </head> <body>
<h3>这里是Other页面</h3>
</body>
</html>
项目图:
最后的话:
补充:
(2014年11月21日第一次补充):
第一点:
第二点:
url ,第一个是 /** ,第二个是 /role1/index.jsp ,第一个当然需要很高的权限了(因为能匹配所有 url
,即可以访问所有 url ),假设它需要的角色是 ROLE_ADMIN (不是一般人拥有的),第二个所需的角色是
ROLE_1 。 当我用 ROLE_1 这个角色访问 /role1/index.jsp
时,在getAttributes方法中,当先迭代了 /** 这个url,它就能匹配 /role1/index.jsp 这个url,并直接返回
/** 这个url对应的所有角色(在这,也就ROLE_ADMIN)给MyAccessDecisionManager这个投票类,
MyAccessDecisionManager这个类中再对比 用户的角色 ROLE_1 ,就会发现不匹配。 最后,明明可以有权访问的
url
,却不能访问了。
注:其实这样不是很严谨,不过笔者这里的对应关系是不变的,单例性不需很强,更严谨的请参考笔者另一篇博文设计模式之单件模式)。
package com.lcy.bookcrossing.springSecurity; import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set; import javax.annotation.Resource; 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 com.lcy.bookcrossing.bean.RoleUrlResource;
import com.lcy.bookcrossing.dao.IRoleUrlResourceDao;
import com.lcy.bookcrossing.springSecurity.tool.AntUrlPathMatcher;
import com.lcy.bookcrossing.springSecurity.tool.UrlMatcher; public class MyInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
private UrlMatcher urlMatcher = new AntUrlPathMatcher();
// private static Map<String, Collection<ConfigAttribute>> resourceMap = null; //将所有的角色和url的对应关系缓存起来
private static List<RoleUrlResource> rus = null; @Resource
private IRoleUrlResourceDao roleUrlDao; //tomcat启动时实例化一次
public MyInvocationSecurityMetadataSource() {
// 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);
} */ //参数是要访问的url,返回这个url对于的所有权限(或角色)
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
// 将参数转为url
String url = ((FilterInvocation)object).getRequestUrl(); //查询所有的url和角色的对应关系
if(rus == null){
rus = roleUrlDao.findAll();
} //匹配所有的url,并对角色去重
Set<String> roles = new HashSet<String>();
for(RoleUrlResource ru : rus){
if (urlMatcher.pathMatchesUrl(ru.getUrlResource().getUrl(), url)) {
roles.add(ru.getRole().getRoleName());
}
}
Collection<ConfigAttribute> cas = new ArrayList<ConfigAttribute>();
for(String role : roles){
ConfigAttribute ca = new SecurityConfig(role);
cas.add(ca);
}
return cas; /*Iterator<String> ite = resourceMap.keySet().iterator();
while (ite.hasNext()) {
String resURL = ite.next();
if (urlMatcher.pathMatchesUrl(resURL, url)) {
return resourceMap.get(resURL);
}
}
return null; */
}
public boolean supports(Class<?>clazz) {
return true;
}
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
}
以上代码,在getAttributes方法中缓存起所有的对应关系(可以使用依赖注入了),并匹配所有 url ,对角色进行去重(因为多个url可能有重复的角色),这样就能修复那个bug了。
(2014年12月10日第二次补充):
<?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="/jsp/css/**" security="none"></http>
<http pattern="/jsp/js/**" security="none"></http>
<http pattern="/jsp/images/**" security="none"></http>
<http pattern="/login.jsp" security="none" />
<http pattern="/accessDenied.jsp" security="none" />
<http pattern="/index.jsp" security="none" /> <http use-expressions='true' entry-point-ref="myAuthenticationEntryPoint" access-denied-page="/accessDenied.jsp"> <!-- 使用自己自定义的登陆认证过滤器 --><!-- 这里一定要注释掉,因为我们需要重写它的过滤器 -->
<!-- <form-login login-page="/login.jsp"
authentication-failure-url="/accessDenied.jsp"
default-target-url="/index.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> <!-- 认证和授权 --><!-- 重写登陆认证的过滤器,使我们可以拿到任何参数 -->
<custom-filter ref="myAuthenticationFilter" position="FORM_LOGIN_FILTER" />
<custom-filter ref="myFilter" before="FILTER_SECURITY_INTERCEPTOR" /> <!-- 登出管理 -->
<logout invalidate-session="true" logout-url="/j_spring_security_logout" /> </http> <!-- 未登录的切入点 --><!-- 需要有个切入点 -->
<b:bean id="myAuthenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<b:property name="loginFormUrl" value="/login.jsp"></b:property>
</b:bean> <!-- 登录验证器:用户有没有登录的资格 --><!-- 这个就是重写的认证过滤器 -->
<b:bean id="myAuthenticationFilter" class="com.lcy.springSecurity.MyAuthenticationFilter">
<b:property name="authenticationManager" ref="authenticationManager" />
<b:property name="filterProcessesUrl" value="/j_spring_security_check" />
<b:property name="authenticationSuccessHandler">
<b:bean class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
<b:property name="defaultTargetUrl" value="/index.jsp" />
</b:bean>
</b:property>
<b:property name="authenticationFailureHandler">
<b:bean class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
<b:property name="defaultFailureUrl" value="/accessDenied.jsp" />
</b:bean>
</b:property>
</b:bean> <!--一个自定义的filter,必须包含 authenticationManager,accessDecisionManager,securityMetadataSource三个属性,我们的所有控制将在这三个类中实现,解释详见具体配置 -->
<b:bean id="myFilter"
class="com.lcy.springSecurity.MyFilterSecurityInterceptor">
<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" /> -->
<!-- <password-encoder hash="md5" /> -->
</authentication-provider>
</authentication-manager>
<!--在这个类中,你就可以从数据库中读入用户的密码,角色信息,是否锁定,账号是否过期等 -->
<b:bean id="myUserDetailService" class="com.lcy.springSecurity.MyUserDetailService" />
<!--访问决策器,决定某个用户具有的角色,是否有足够的权限去访问某个资源 -->
<b:bean id="myAccessDecisionManagerBean"
class="com.lcy.springSecurity.MyAccessDecisionManager">
</b:bean>
<!--资源源数据定义,将所有的资源和权限对应关系建立起来,即定义某一资源可以被哪些角色访问 -->
<b:bean id="securityMetadataSource"
class="com.lcy.springSecurity.MyInvocationSecurityMetadataSource" /> </b:beans>
我现在的项目需要的是,角色只要管理员、教师、学生,所以MyAuthenticationFilter(重写的认证过滤器):
package com.lcy.springSecurity; import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import com.lcy.dao.IAdminDao;
import com.lcy.dao.IStudentDao;
import com.lcy.dao.ITeacherDao;
import com.lcy.entity.Admin;
import com.lcy.entity.Student;
import com.lcy.entity.Teacher; public class MyAuthenticationFilter extends UsernamePasswordAuthenticationFilter { private static final String USERNAME = "username";
private static final String PASSWORD = "password"; @Resource
private IStudentDao studentdao;
@Resource
private ITeacherDao teacherdao;
@Resource
private IAdminDao admindao; @Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (!request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
} String username = obtainUsername(request);
String password = obtainPassword(request);
String roletype = request.getParameter("roletype"); username = username.trim(); UsernamePasswordAuthenticationToken authRequest = null; if(!"".equals(roletype) || roletype != null){
if("student".equals(roletype)){
Student stu = studentdao.findById(username); //通过session把用户对象设置到session中
request.getSession().setAttribute("session_user", stu); //将角色标志在username上
username = "stu"+username; try {
if (stu == null || !stu.getPassword().equals(password)) {
BadCredentialsException exception = new BadCredentialsException("用户名或密码不匹配");
throw exception;
}
} catch (Exception e) {
BadCredentialsException exception = new BadCredentialsException("没有此用户");
throw exception;
} }else if("teacher".equals(roletype)){
Teacher tea = teacherdao.findById(username); //通过session把用户对象设置到session中
request.getSession().setAttribute("session_user", tea); //将角色标志在username上
username = "tea"+username; try {
if (tea == null || !tea.getPassword().equals(password)) {
BadCredentialsException exception = new BadCredentialsException("用户名或密码不匹配");
throw exception;
}
} catch (Exception e) {
BadCredentialsException exception = new BadCredentialsException("没有此用户");
throw exception;
} }else if("admin".equals(roletype)){
Admin adm = admindao.findById(username); //通过session把用户对象设置到session中
request.getSession().setAttribute("session_user", adm); //将角色标志在username上
username = "adm"+username;
try {
if (adm == null || !password.equals(adm.getPassword())) {
BadCredentialsException exception = new BadCredentialsException("用户名或密码不匹配");
throw exception;
}
} catch (Exception e) {
BadCredentialsException exception = new BadCredentialsException("没有此用户");
throw exception;
} }else{
BadCredentialsException exception = new BadCredentialsException("系统错误:没有对应的角色!");
throw exception;
}
} //实现验证
authRequest = new UsernamePasswordAuthenticationToken(username, password);
//允许设置用户详细属性
setDetails(request, authRequest);
//运行
return this.getAuthenticationManager().authenticate(authRequest);
} @Override
protected String obtainUsername(HttpServletRequest request) {
Object obj = request.getParameter(USERNAME);
return null == obj ? "" : obj.toString();
} @Override
protected String obtainPassword(HttpServletRequest request) {
Object obj = request.getParameter(PASSWORD);
return null == obj ? "" : obj.toString();
} @Override
protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
super.setDetails(request, authRequest);
}
}
笔者自己断点可知,执行完上面那个认证过滤器,才会执行MyUserDetailService。
username)
方法只能接收一个参数username,而且这个username是从认证过滤器那里传过来的,所以笔者就通过username顺带传递角色类型过来,如上面认证过滤器,将角色类型拼在username中。到MyUserDetailService类在解开。(如有更好的方法,请评论告知,谢谢)
MyUserDetailService:
package com.lcy.springSecurity; import java.util.ArrayList;
import java.util.Collection; import javax.annotation.Resource; 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 com.lcy.dao.IAdminDao;
import com.lcy.dao.IStudentDao;
import com.lcy.dao.ITeacherDao;
import com.lcy.entity.Admin;
import com.lcy.entity.Student;
import com.lcy.entity.Teacher; public class MyUserDetailService implements UserDetailsService {
@Resource
private IStudentDao studentdao;
@Resource
private ITeacherDao teacherdao;
@Resource
private IAdminDao admindao; //登陆验证时,通过username获取用户的所有权限信息,
//并返回User放到spring的全局缓存SecurityContextHolder中,以供授权器使用
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException {
Collection<GrantedAuthority> auths= new ArrayList<GrantedAuthority>();
//获取角色标志
String roletype = username.substring(0,3);
username = username.substring(3);
String password = ""; if("stu".equals(roletype)){
Student stu = studentdao.findById(username);
password = stu.getPassword();
auths.add(new SimpleGrantedAuthority("ROLE_STU"));
}else if("tea".equals(roletype)){
Teacher tea = teacherdao.findById(username);
password = tea.getPassword();
auths.add(new SimpleGrantedAuthority("ROLE_TEA"));
}else if("adm".equals(roletype)){
Admin adm = admindao.findById(username);
password = adm.getPassword();
auths.add(new SimpleGrantedAuthority("ROLE_ADM"));
} User user = new User(username, password, true, true, true, true, auths);
return user;
}
}