首页 > 程序开发 > 软件开发 > 其他 >

spring security的原理及教程

2018-04-11

spring security的原理及教程。如何使用spring security,相信百度过的都知道,总共有四种用法,从简到深为:1、不用数据库,全部数据写在配置文件,这个也是官方文档里面的demo。

spring security使用分类:

如何使用spring security,相信百度过的都知道,总共有四种用法,从简到深为:1、不用数据库,全部数据写在配置文件,这个也是官方文档里面的demo;2、使用数据库,根据spring security默认实现代码设计数据库,也就是说数据库已经固定了,这种方法不灵活,而且那个数据库设计得很简陋,实用性差;3、spring security和Acegi不同,它不能修改默认filter了,但支持插入filter,所以根据这个,我们可以插入自己的filter来灵活使用;4、暴力手段,修改源码,前面说的修改默认filter只是修改配置文件以替换filter而已,这种是直接改了里面的源码,但是这种不符合OO设计原则,而且不实际,不可用。

本文面向读者:

因为本文准备介绍第三种方法,所以面向的读者是已经具备了spring security基础知识的。

spring security的简单原理:

使用众多的拦截器对url拦截,以此来管理权限。但是这么多拦截器,笔者不可能对其一一来讲,主要讲里面核心流程的两个。

首先,权限管理离不开登陆验证的,所以登陆验证拦截器AuthenticationProcessingFilter要讲;

还有就是对访问的资源管理吧,所以资源管理拦截器AbstractSecurityInterceptor要讲;

但拦截器里面的实现需要一些组件来实现,所以就有了AuthenticationManager、accessDecisionManager等组件来支撑。

现在先大概过一遍整个流程,用户登陆,会被AuthenticationProcessingFilter拦截,调用AuthenticationManager的实现,而且AuthenticationManager会调用ProviderManager来获取用户验证信息(不同的Provider调用的服务不同,因为这些信息可以是在数据库上,可以是在LDAP服务器上,可以是xml配置文件上等),如果验证通过后会将用户的权限信息封装一个User放到spring的全局缓存SecurityContextHolder中,以备后面访问资源时使用。

访问资源(即授权管理),访问url时,会通过AbstractSecurityInterceptor拦截器拦截,其中会调用FilterInvocationSecurityMetadataSource的方法来获取被拦截url所需的全部权限,在调用授权管理器AccessDecisionManager,这个授权管理器会通过spring的全局缓存SecurityContextHolder获取用户的权限信息,还会获取被拦截的url和被拦截url所需的全部权限,然后根据所配的策略(有:一票决定,一票否定,少数服从多数等),如果权限足够,则返回,权限不够则报错并调用权限不足页面。

虽然讲得好像好复杂,读者们可能有点晕,不过不打紧,真正通过代码的讲解在后面,读者可以看完后面的代码实现,再返回看这个简单的原理,可能会有不错的收获。

spring security使用实现(基于spring security3.1.4):

javaEE的入口:web.xml:

[html]view plaincopy

xsi:schemaLocation="http://java.sun.com/xml/ns/javaeehttp://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

contextConfigLocation

classpath:securityConfig.xml

springSecurityFilterChain

org.springframework.web.filter.DelegatingFilterProxy

springSecurityFilterChain

/*

org.springframework.web.context.ContextLoaderListener

index.jsp

上面那个配置不用多说了吧

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.erdangjiade.spring.security.MyFilterSecurityInterceptor">

class="com.erdangjiade.spring.security.MyAccessDecisionManager">

class="com.erdangjiade.spring.security.MyInvocationSecurityMetadataSource"/>

其实所有配置都在里面,首先这个版本的spring security不支持了filter=none的配置了,改成了独立的">

这里是Other页面

项目图:

\

最后的话:

虽然笔者没给读者们demo,但是所有源码和jar包都在这个教程里面,为什么不直接给?笔者的目的是让读者跟着教程敲一遍代码,使印象深刻(相信做这行的都知道,同样一段代码,看过和敲过的区别是多么的大),所以不惜如此来强迫大家了。

第一点:

MyUserDetailService这个类负责的是只是获取登陆用户的详细信息(包括密码、角色等),不负责和前端传过来的密码对比,只需返回User对象,后会有其他类根据User对象对比密码的正确性(框架帮我们做)。

第二点:

记得MyInvocationSecurityMetadataSource这个类是负责的是获取角色与url资源的所有对应关系,并根据url查询对应的所有角色。

今天为一个项目搭安全架构时,第一,发现上面MyInvocationSecurityMetadataSource这个类的代码有个bug:

上面的代码中,将所有的对应关系缓存到resourceMap,key是url,value是这个url对应所有角色。

getAttributes方法中,只要匹配到一个url就返回这个url对应所有角色,不再匹配后面的url,问题来了,当url有交集时,就有可能漏掉一些角色了:如有两个 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 ,却不能访问了。

第二,之前不是说缓存所有对应关系,需要读者自己写sessionFactory(因为在实例化这个类时,配置的sessionFactory可能还没实例化或dao还没加载好),既然这样,那笔者可以不在构造方法中加载对应关系,可以在第一次调用getAttributes方法时再加载(用静态变量缓存起来,第二次就不用再加载了, 注:其实这样不是很严谨,不过笔者这里的对应关系是不变的,单例性不需很强,更严谨的请参考笔者另一篇博文设计模式之单件模式)。

修改过的MyInvocationSecurityMetadataSource类:

[java]view plaincopy

packagecom.lcy.bookcrossing.springSecurity;

importjava.util.ArrayList;

importjava.util.Collection;

importjava.util.HashMap;

importjava.util.HashSet;

importjava.util.Iterator;

importjava.util.List;

importjava.util.Map;

importjava.util.Set;

importjavax.annotation.Resource;

importorg.springframework.security.access.ConfigAttribute;

importorg.springframework.security.access.SecurityConfig;

importorg.springframework.security.web.FilterInvocation;

importorg.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;

importcom.lcy.bookcrossing.bean.RoleUrlResource;

importcom.lcy.bookcrossing.dao.IRoleUrlResourceDao;

importcom.lcy.bookcrossing.springSecurity.tool.AntUrlPathMatcher;

importcom.lcy.bookcrossing.springSecurity.tool.UrlMatcher;

publicclassMyInvocationSecurityMetadataSourceimplementsFilterInvocationSecurityMetadataSource{

privateUrlMatcherurlMatcher=newAntUrlPathMatcher();

//privatestaticMap>resourceMap=null;

//将所有的角色和url的对应关系缓存起来

privatestaticListrus=null;

@Resource

privateIRoleUrlResourceDaoroleUrlDao;

//tomcat启动时实例化一次

publicMyInvocationSecurityMetadataSource(){

//loadResourceDefine();

}

//tomcat开启时加载一次,加载所有url和权限(或角色)的对应关系

/*privatevoidloadResourceDefine(){

resourceMap=newHashMap>();

Collectionatts=newArrayList();

ConfigAttributeca=newSecurityConfig("ROLE_USER");

atts.add(ca);

resourceMap.put("/index.jsp",atts);

Collectionattsno=newArrayList();

ConfigAttributecano=newSecurityConfig("ROLE_NO");

attsno.add(cano);

resourceMap.put("/other.jsp",attsno);

}*/

//参数是要访问的url,返回这个url对于的所有权限(或角色)

publicCollectiongetAttributes(Objectobject)throwsIllegalArgumentException{

//将参数转为url

Stringurl=((FilterInvocation)object).getRequestUrl();

//查询所有的url和角色的对应关系

if(rus==null){

rus=roleUrlDao.findAll();

}

//匹配所有的url,并对角色去重

Setroles=newHashSet();

for(RoleUrlResourceru:rus){

if(urlMatcher.pathMatchesUrl(ru.getUrlResource().getUrl(),url)){

roles.add(ru.getRole().getRoleName());

}

}

Collectioncas=newArrayList();

for(Stringrole:roles){

ConfigAttributeca=newSecurityConfig(role);

cas.add(ca);

}

returncas;

/*Iteratorite=resourceMap.keySet().iterator();

while(ite.hasNext()){

StringresURL=ite.next();

if(urlMatcher.pathMatchesUrl(resURL,url)){

returnresourceMap.get(resURL);

}

}

returnnull;*/

}

publicbooleansupports(Classclazz){

returntrue;

}

publicCollectiongetAllConfigAttributes(){

returnnull;

}

}

以上代码,在getAttributes方法中缓存起所有的对应关系(可以使用依赖注入了),并匹配所有url ,对角色进行去重(因为多个url可能有重复的角色),这样就能修复那个bug了。

转载请标注本文链接:http://blog.csdn.net/u012367513/article/details/38866465

(2014年12月10日第二次补充):

这次补充不是修上面的bug,而是添加新功能。

我们知道,上面的实现的登陆界面只能传递两个参数(j_username,j_password),而且是固定的。

总是有一个项目需求,我们的角色(ROLE_)不是很多,只需在登陆界面选择一种角色就行了,那么如何将角色类型传递到spring security呢,现在笔者对配置文件再修改修改:

[html]view plaincopy

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.lcy.springSecurity.MyFilterSecurityInterceptor">

class="com.lcy.springSecurity.MyAccessDecisionManager">

class="com.lcy.springSecurity.MyInvocationSecurityMetadataSource"/>

我现在的项目需要的是,角色只要管理员、教师、学生,所以MyAuthenticationFilter(重写的认证过滤器):

[java]view plaincopy

packagecom.lcy.springSecurity;

importjavax.annotation.Resource;

importjavax.servlet.http.HttpServletRequest;

importjavax.servlet.http.HttpServletResponse;

importorg.springframework.security.authentication.AuthenticationServiceException;

importorg.springframework.security.authentication.BadCredentialsException;

importorg.springframework.security.authentication.UsernamePasswordAuthenticationToken;

importorg.springframework.security.core.Authentication;

importorg.springframework.security.core.AuthenticationException;

importorg.springframework.security.core.context.SecurityContextHolder;

importorg.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

importcom.lcy.dao.IAdminDao;

importcom.lcy.dao.IStudentDao;

importcom.lcy.dao.ITeacherDao;

importcom.lcy.entity.Admin;

importcom.lcy.entity.Student;

importcom.lcy.entity.Teacher;

publicclassMyAuthenticationFilterextendsUsernamePasswordAuthenticationFilter{

privatestaticfinalStringUSERNAME="username";

privatestaticfinalStringPASSWORD="password";

@Resource

privateIStudentDaostudentdao;

@Resource

privateITeacherDaoteacherdao;

@Resource

privateIAdminDaoadmindao;

@Override

publicAuthenticationattemptAuthentication(HttpServletRequestrequest,HttpServletResponseresponse)throwsAuthenticationException{

if(!request.getMethod().equals("POST")){

thrownewAuthenticationServiceException("Authenticationmethodnotsupported:"+request.getMethod());

}

Stringusername=obtainUsername(request);

Stringpassword=obtainPassword(request);

Stringroletype=request.getParameter("roletype");

username=username.trim();

UsernamePasswordAuthenticationTokenauthRequest=null;

if(!"".equals(roletype)||roletype!=null){

if("student".equals(roletype)){

Studentstu=studentdao.findById(username);

//通过session把用户对象设置到session中

request.getSession().setAttribute("session_user",stu);

//将角色标志在username上

username="stu"+username;

try{

if(stu==null||!stu.getPassword().equals(password)){

BadCredentialsExceptionexception=newBadCredentialsException("用户名或密码不匹配");

throwexception;

}

}catch(Exceptione){

BadCredentialsExceptionexception=newBadCredentialsException("没有此用户");

throwexception;

}

}elseif("teacher".equals(roletype)){

Teachertea=teacherdao.findById(username);

//通过session把用户对象设置到session中

request.getSession().setAttribute("session_user",tea);

//将角色标志在username上

username="tea"+username;

try{

if(tea==null||!tea.getPassword().equals(password)){

BadCredentialsExceptionexception=newBadCredentialsException("用户名或密码不匹配");

throwexception;

}

}catch(Exceptione){

BadCredentialsExceptionexception=newBadCredentialsException("没有此用户");

throwexception;

}

}elseif("admin".equals(roletype)){

Adminadm=admindao.findById(username);

//通过session把用户对象设置到session中

request.getSession().setAttribute("session_user",adm);

//将角色标志在username上

username="adm"+username;

try{

if(adm==null||!password.equals(adm.getPassword())){

BadCredentialsExceptionexception=newBadCredentialsException("用户名或密码不匹配");

throwexception;

}

}catch(Exceptione){

BadCredentialsExceptionexception=newBadCredentialsException("没有此用户");

throwexception;

}

}else{

BadCredentialsExceptionexception=newBadCredentialsException("系统错误:没有对应的角色!");

throwexception;

}

}

//实现验证

authRequest=newUsernamePasswordAuthenticationToken(username,password);

//允许设置用户详细属性

setDetails(request,authRequest);

//运行

returnthis.getAuthenticationManager().authenticate(authRequest);

}

@Override

protectedStringobtainUsername(HttpServletRequestrequest){

Objectobj=request.getParameter(USERNAME);

returnnull==obj"":obj.toString();

}

@Override

protectedStringobtainPassword(HttpServletRequestrequest){

Objectobj=request.getParameter(PASSWORD);

returnnull==obj"":obj.toString();

}

@Override

protectedvoidsetDetails(HttpServletRequestrequest,UsernamePasswordAuthenticationTokenauthRequest){

super.setDetails(request,authRequest);

}

}

笔者自己断点可知,执行完上面那个认证过滤器,才会执行MyUserDetailService。MyUserDetailService:

packagecom.lcy.springSecurity;

importjava.util.ArrayList;

importjava.util.Collection;

importjavax.annotation.Resource;

importorg.springframework.dao.DataAccessException;

importorg.springframework.security.core.GrantedAuthority;

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;

importcom.lcy.dao.IAdminDao;

importcom.lcy.dao.IStudentDao;

importcom.lcy.dao.ITeacherDao;

importcom.lcy.entity.Admin;

importcom.lcy.entity.Student;

importcom.lcy.entity.Teacher;

publicclassMyUserDetailServiceimplementsUserDetailsService{

@Resource

privateIStudentDaostudentdao;

@Resource

privateITeacherDaoteacherdao;

@Resource

privateIAdminDaoadmindao;

//登陆验证时,通过username获取用户的所有权限信息,

//并返回User放到spring的全局缓存SecurityContextHolder中,以供授权器使用

publicUserDetailsloadUserByUsername(Stringusername)

throwsUsernameNotFoundException,DataAccessException{

Collectionauths=newArrayList();

//获取角色标志

Stringroletype=username.substring(0,3);

username=username.substring(3);

Stringpassword="";

if("stu".equals(roletype)){

Studentstu=studentdao.findById(username);

password=stu.getPassword();

auths.add(newSimpleGrantedAuthority("ROLE_STU"));

}elseif("tea".equals(roletype)){

Teachertea=teacherdao.findById(username);

password=tea.getPassword();

auths.add(newSimpleGrantedAuthority("ROLE_TEA"));

}elseif("adm".equals(roletype)){

Adminadm=admindao.findById(username);

password=adm.getPassword();

auths.add(newSimpleGrantedAuthority("ROLE_ADM"));

}

Useruser=newUser(username,password,true,true,true,true,auths);

returnuser;

}

}

相关文章
最新文章
热点推荐