SpringSecurity学习一-控制同个帐号当前只能有一个登录
Date:2016-09-14
Author:Kagula
Envronment:
[1]JDK1.7.0_79
[2]apache-tomcat-7.0.68
[3]EclipseMars2
Introduction:
据《springsecurity的原理及教程》能实现
[a]踢出其它地方登录的这个帐号。
[b]或者已经登录了,就不允许再登录。
理论部份参考资料[1]《springsecurity的原理及教程》
这里只贴代码
示例由6个class、6个jsp、三个配置文件组成
web.xml
[html]viewplaincopy在CODE上查看代码片派生到我的代码片
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaeehttp://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="schedule-console"version="3.0">
ArchetypeCreatedWebApplication
contextConfigLocation
classpath:securityConfig.xml
springSecurityFilterChain
org.springframework.web.filter.DelegatingFilterProxy
springSecurityFilterChain
/
org.springframework.web.context.ContextLoaderListener
index.jsp
pom.xml
[html]viewplaincopy在CODE上查看代码片派生到我的代码片
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0http://maven.apache.org/maven-v4_0_0.xsd">
4.0.0
com.nuoke
testSpringSecurity
war
0.0.1-SNAPSHOT
testSpringSecurityMavenWebapp
http://maven.apache.org
UTF-8
UTF-8
3.1.2.RELEASE
org.springframework
spring-core
${spring.version}
org.springframework
spring-beans
${spring.version}
org.springframework
spring-webmvc
${spring.version}
org.springframework.security
spring-security-core
${spring.version}
org.springframework.security
spring-security-web
${spring.version}
org.springframework.security
spring-security-config
${spring.version}
org.springframework.security
spring-security-taglibs
${spring.version}
testSpringSecurity
org.apache.maven.plugins
maven-compiler-plugin
3.0
1.7
securityConfig.xml
这个文件新建的时候放在“src/main/resouces”节点下
[html]viewplaincopy在CODE上查看代码片派生到我的代码片
xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/securityhttp://www.springframework.org/schema/security/spring-security-3.1.xsd">
error-if-maximum-exceeded="false"/>
class="com.nuoke.MyFilterSecurityInterceptor">
class="com.nuoke.MyAccessDecisionManager">
class="com.nuoke.MyInvocationSecurityMetadataSource"/>
java文件有6个,其中前面四个是关键。
MyAccessDecisionManager.java
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
packagecom.nuoke;
importjava.util.Collection;
importjava.util.Iterator;
importorg.springframework.security.access.AccessDecisionManager;
importorg.springframework.security.access.AccessDeniedException;
importorg.springframework.security.access.ConfigAttribute;
importorg.springframework.security.access.SecurityConfig;
importorg.springframework.security.authentication.InsufficientAuthenticationException;
importorg.springframework.security.core.Authentication;
importorg.springframework.security.core.GrantedAuthority;
publicclassMyAccessDecisionManagerimplementsAccessDecisionManager{
//检查用户是否够权限访问资源
//参数authentication是从spring的全局缓存SecurityContextHolder中拿到的,里面是用户的权限信息
//参数object是url
//参数configAttributes所需的权限
@Override
publicvoiddecide(Authenticationauthentication,
Objectobject,
CollectionconfigAttributes)
throwsAccessDeniedException,InsufficientAuthenticationException
{
if(configAttributes==null){
return;
}
Iteratorite=configAttributes.iterator();
while(ite.hasNext()){
ConfigAttributeca=ite.next();
StringneedRole=((SecurityConfig)ca).getAttribute();
for(GrantedAuthorityga:authentication.getAuthorities()){
if(needRole.equals(ga.getAuthority())){
return;
}
}
}
//注意:执行这里,后台是会抛异常的,但是界面会跳转到所配的access-denied-page页面
thrownewAccessDeniedException("noright");
}
publicbooleansupports(ConfigAttributeattribute){
returntrue;
}
@Override
publicbooleansupports(Class>arg0){
returntrue;
}
}
MyFilterSecurityInterceptor.java
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
packagecom.nuoke;
importjava.io.IOException;
importjavax.servlet.Filter;
importjavax.servlet.FilterChain;
importjavax.servlet.FilterConfig;
importjavax.servlet.ServletException;
importjavax.servlet.ServletRequest;
importjavax.servlet.ServletResponse;
importorg.springframework.security.access.SecurityMetadataSource;
importorg.springframework.security.access.intercept.AbstractSecurityInterceptor;
importorg.springframework.security.access.intercept.InterceptorStatusToken;
importorg.springframework.security.web.FilterInvocation;
importorg.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
/
继承AbstractSecurityInterceptor、实现Filter是必须的。
首先,登陆后,每次访问资源都会被这个拦截器拦截,会执行doFilter这个方法,
这个方法调用了invoke方法,其中fi断点显示是一个url(可能重写了toString方法吧,但是里面还有一些方法的),最重要的是beforeInvocation这个方法,
它首先会调用MyInvocationSecurityMetadataSource类的getAttributes方法获取被拦截url所需的权限,
then调用MyAccessDecisionManager类decide方法判断用户是否够权限。弄完这一切就会执行下一个拦截器.
/
publicclassMyFilterSecurityInterceptorextendsAbstractSecurityInterceptorimplementsFilter{
//配置文件注入
privateFilterInvocationSecurityMetadataSourcesecurityMetadataSource;
//登陆后,每次访问资源都通过这个拦截器拦截
publicvoiddoFilter(ServletRequestrequest,ServletResponseresponse,FilterChainchain)
throwsIOException,ServletException{
FilterInvocationfi=newFilterInvocation(request,response,chain);
invoke(fi);
}
publicFilterInvocationSecurityMetadataSourcegetSecurityMetadataSource(){
returnthis.securityMetadataSource;
}
@Override
publicClassgetSecureObjectClass(){
returnFilterInvocation.class;
}
publicvoidinvoke(FilterInvocationfi)throwsIOException,ServletException{
//fi里面有一个被拦截的url
//里面调用MyInvocationSecurityMetadataSource的getAttributes(Objectobject)这个方法获取fi对应的所有权限
//再调用MyAccessDecisionManager的decide方法来校验用户的权限是否足够
InterceptorStatusTokentoken=super.beforeInvocation(fi);
try{
//执行下一个拦截器
fi.getChain().doFilter(fi.getRequest(),fi.getResponse());
}finally{
super.afterInvocation(token,null);
}
}
publicSecurityMetadataSourceobtainSecurityMetadataSource(){
returnthis.securityMetadataSource;
}
publicvoidsetSecurityMetadataSource(
FilterInvocationSecurityMetadataSourcenewSource)
{
this.securityMetadataSource=newSource;
}
publicvoiddestroy(){
}
publicvoidinit(FilterConfigarg0)throwsServletException{
}
}
MyInvocationSecurityMetadataSource.java
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
packagecom.nuoke;
importjava.util.ArrayList;
importjava.util.Collection;
importjava.util.HashMap;
importjava.util.Iterator;
importjava.util.Map;
importorg.springframework.security.access.ConfigAttribute;
importorg.springframework.security.access.SecurityConfig;
importorg.springframework.security.web.FilterInvocation;
importorg.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
publicclassMyInvocationSecurityMetadataSource
implementsFilterInvocationSecurityMetadataSource{
privateUrlMatcherurlMatcher=newAntUrlPathMatcher();
privatestaticMap>resourceMap=null;
//tomcat启动时实例化一次
publicMyInvocationSecurityMetadataSource(){
//这个类的实例化只在web服务器启动时调用一次,那就是说loadResourceDefine方法只会调用一次
//所以只适合页面的权限不再更改的情况。
loadResourceDefine();
}
//tomcat开启时加载一次,加载所有url和权限(或角色)的对应关系
privatevoidloadResourceDefine(){
resourceMap=newHashMap>();
//需要ROLE_USER角色登录后才能访问的页面。
Collectionatts=newArrayList();
ConfigAttributeca=newSecurityConfig("ROLE_USER");
atts.add(ca);
resourceMap.put("/index.jsp",atts);
resourceMap.put("/admin.jsp",atts);
//任何用户都没有进入/other.jsp权限
Collectionattsno=newArrayList();
ConfigAttributecano=newSecurityConfig("ROLE_NO");
attsno.add(cano);
resourceMap.put("/accessDenied.jsp",attsno);
//当url有交集时,就有可能漏掉一些角色
}
//参数是要访问的url,返回这个url对于的所有权限(或角色)
publicCollectiongetAttributes(Objectobject)
throwsIllegalArgumentException{
//将参数转为url
Stringurl=((FilterInvocation)object).getRequestUrl();
Iteratorite=resourceMap.keySet().iterator();
while(ite.hasNext()){
StringresURL=ite.next();
if(urlMatcher.pathMatchesUrl(resURL,url)){
returnresourceMap.get(resURL);
}
}
returnnull;
}
@Override
publicbooleansupports(Class>arg0){
returntrue;
}
publicCollectiongetAllConfigAttributes(){
returnnull;
}
}
MyUserDetailService.java
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
packagecom.nuoke;
importjava.util.ArrayList;
importjava.util.Collection;
importorg.springframework.dao.DataAccessException;
importorg.springframework.security.core.GrantedAuthority;
importorg.springframework.security.core.authority.GrantedAuthorityImpl;
importorg.springframework.security.core.authority.SimpleGrantedAuthority;
importorg.springframework.security.core.userdetails.User;
importorg.springframework.security.core.userdetails.UserDetails;
importorg.springframework.security.core.userdetails.UserDetailsService;
importorg.springframework.security.core.userdetails.UsernameNotFoundException;
publicclassMyUserDetailServiceimplementsUserDetailsService{
//登陆验证时,通过username获取用户的所有权限信息,
//并返回User放到spring的全局缓存SecurityContextHolder中,以供授权器使用
publicUserDetailsloadUserByUsername(Stringusername)
throwsUsernameNotFoundException,DataAccessException{
Collectionauths=newArrayList();
SimpleGrantedAuthorityauth2=newSimpleGrantedAuthority("ROLE_ADMIN");
SimpleGrantedAuthorityauth1=newSimpleGrantedAuthority("ROLE_USER");
if(username.equals("admin")){
auths=newArrayList();
auths.add(auth1);
auths.add(auth2);
}
//第二个参数是密码。
Useruser=newUser(username,"123",true,true,true,true,auths);
returnuser;
}
}
UrlMatcher.java
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
packagecom.nuoke;
publicinterfaceUrlMatcher{
Objectcompile(StringparamString);
booleanpathMatchesUrl(ObjectparamObject,StringparamString);
StringgetUnivewww.shanxiwang.netrsalMatchPattern();
booleanrequiresLowerCaseUrl();
}
AntUrlPathMatcher.java
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
packagecom.nuoke;
importorg.springframework.util.AntPathMatcher;
importorg.springframework.util.PathMatcher;
publicclassAntUrlPathMatcherimplementsUrlMatcher{
privatebooleanrequiresLowerCaseUrl;
privatePathMatcherpathMatcher;
publicAntUrlPathMatcher(){
this(true);
}
publicAntUrlPathMatcher(booleanrequiresLowerCaseUrl)
{
this.requiresLowerCaseUrl=true;
this.pathMatcher=newAntPathMatcher();
this.requiresLowerCaseUrl=requiresLowerCaseUrl;
}
publicObjectcompile(Stringpath){
if(this.requiresLowerCaseUrl){
returnpath.toLowerCase();
}
returnpath;
}
publicvoidsetRequiresLowerCaseUrl(booleanrequiresLowerCaseUrl){
this.requiresLowerCaseUrl=requiresLowerCaseUrl;
}
publicbooleanpathMatchesUrl(Objectpath,Stringurl){
if(("/".equals(path))||("".equals(path))){
returntrue;
}
returnthis.pathMatcher.match((String)path,url);
}
publicStringgetUniversalMatchPattern(){
return"/";
}
publicbooleanrequiresLowerCaseUrl(){
returnthis.requiresLowerCaseUrl;
}
publicStringtoString(){
returnsuper.getClass().getName()+"[requiresLowerCase=''"+this.requiresLowerCaseUrl+"'']";
}
}
jsp文件共有6个。
accessDenied.jsp
[html]viewplaincopy在CODE上查看代码片派生到我的代码片
<%@pagelanguage="java"import="java.util."pageEncoding="utf-8"%>
MyJSP''accessDenied.jsp''startingpage
accessDenied.
admin.jsp
[html]viewplaincopy在CODE上查看代码片派生到我的代码片
<%@pagelanguage="java"import="java.util.,java.text."pageEncoding="utf-8"%>
MyJSP''admin.jsp''startingpage
欢迎来到管理员页面.
<%
Datedate=newDate();
SimpleDateFormatt=newSimpleDateFormat("yyyy-MM-ddHH:mm:ss");
Stringtime=t.format(date);
%>
当前时间:<%=time%>
index.jsp
[html]viewplaincopy在CODE上查看代码片派生到我的代码片
<%@pagelanguage="java"import="java.util.,java.text."pageEncoding="UTF-8"%>
<%@pagecontentType="text/html;charset=utf-8"%>
<%@taglibprefix="sec"uri="http://www.springframework.org/security/tags"%>
MyJSP''index.jsp''startingpage
这是首页欢迎
!
进入admin页面
进入其它页面
<%
Datedate=newDate();
SimpleDateFormatt=newSimpleDateFormat("yyyy-MM-ddHH:mm:ss");
Stringtime=t.format(date);
%>
当前时间:<%=time%>
login.jsp
[html]viewplaincopy在CODE上查看代码片派生到我的代码片
<%@pagelanguage="java"import="java.util."pageEncoding="UTF-8"%>
登录
other.jsp
[html]viewplaincopy在CODE上查看代码片派生到我的代码片
<%@pagelanguage="java"import="java.util."pageEncoding="UTF-8"%>
<%
Stringpath=request.getContextPath();
StringbasePath=request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
">
MyJSP''other.jsp''startingpage
这里是Other页面,不需要任何权限就可以访问
sessionexpired.jsp
[html]viewplaincopy在CODE上查看代码片派生到我的代码片
<%@pagelanguage="java"import="java.util."pageEncoding="UTF-8"%>
<%
Stringpath=request.getContextPath();
StringbasePath=request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
">
session过期
session过期
备注:
Q如何在页面上判断是否登录?springsecurity3.1
springsecurity登录成功后,会把用户名信息保存在保存在session里面,
其中key为:SPRING_SECURITY_LAST_USERNAME,那么你只需要判断session中这个key是否有值即可
|
|