本文目的是让人能理解spring security ,进而更好的编代码,和设计数据库。如果你想直接拷贝然后跑程序,请自动滤过本文。
先看原理 http://www.importnew.com/20612.html
http://www.blogjava.net/youxia/archive/2008/12/07/244883.html(用户-角色 -权限表);
http://wiki.jikexueyuan.com/project/spring-security/certification.html;
http://www.blogjava.net/youxia/archive/2008/12/07/244883.html;
本为依照http://www.iteye.com/blogs/subjects/spring_security;
Spring Security,这是一种基于 Spring AOP 和 Servlet 过滤器的安全框架。它提供全面的安全性解决方案,同时在 Web 请求级和方法调用级处理身份确认和授权。大家都知道,如果要对Web资源进行保护,最好的办法莫过于Filter,要想对方法调用进行保护,最好的办法莫过于AOP。Acegi对Web资源的保护,就是靠Filter实现的。如下图:
图①
在Spring Security中,AuthenticationManager的默认实现是ProviderManager,而且它不直接自己处理认证请求,而是委托给其所配置的AuthenticationProvider列表,然后会依次使用每一个AuthenticationProvider进行认证,如果有一个AuthenticationProvider认证后的结果不为null,则表示该AuthenticationProvider已经认证成功,之后的AuthenticationProvider将不再继续认证。然后直接以该AuthenticationProvider的认证结果作为ProviderManager的认证结果。如果所有的AuthenticationProvider的认证结果都为null,则表示认证失败,将抛出一个ProviderNotFoundException。
校验认证请求最常用的方法是根据请求的用户名加载对应的UserDetails,然后比对UserDetails的密码与认证请求的密码是否一致,一致则表示认证通过。Spring Security内部的DaoAuthenticationProvider就是使用的这种方式。其内部使用UserDetailsService来负责载UserDetails。在认证成功以后会使用加载的UserDetails来封装要返回的Authentication对象,加载的UserDetails对象是包含用户权限等信息的。认证成功返回的Authentication对象将会保存在当前的SecurityContext中。
当我们在使用NameSpace时, authentication-manager元素的使用会使Spring Security 在内部创建一个ProviderManager,然后可以通过authentication-provider元素往其中添加AuthenticationProvider。当定义authentication-provider元素时,如果没有通过ref属性指定关联哪个AuthenticationProvider,Spring Security默认就会使用DaoAuthenticationProvider。使用了NameSpace后我们就不要再声明ProviderManager了。
默认情况下,在认证成功后ProviderManager将清除返回的Authentication中的凭证信息,如密码。所以如果你在无状态的应用中将返回的Authentication信息缓存起来了,那么以后你再利用缓存的信息去认证将会失败,因为它已经不存在密码这样的凭证信息了。所以在使用缓存的时候你应该考虑到这个问题。一种解决办法是设置ProviderManager的eraseCredentialsAfterAuthentication 属性为false,或者想办法在缓存时将凭证信息一起缓存。
Spring Security的底层是维护一个过滤器链(通过一系列的Filter来管理的,你可以添加自己的过滤器到列表中),每个Filter都有其自身的功能,而且各个Filter在功能上还有关联关系,所以它们的顺序也是非常重要的。https://springcloud.cc/spring-security-zhcn.html
Acegi中提供的Filter不少,有十多个,一个一个学起来比较复杂。但是对于我们Web开发者来说,常用的就那么几个,如下图中的被红圈圈标记出来的,不止这些:
图②
从上到下,它们实现的功能依次是1、制定必须为https连接;2、从Session中提取用户的认证信息;3、退出登录;4、登录;5、记住用户;6、所有的应用必须配置这个Filter。
一般来说,我们写Web应用只需要熟悉这几个Filter就可以了,如果不需要https连接,连第一个也不用熟悉。但是有人肯定会想,这些Filter怎么和我的数据库联系起来呢?不用着急,这些Filter并不直接处理用户的认证,也不直接处理用户的授权,而是把它们交给了认证管理器和决策管理器。
要实现自己的UserDetailService,它就是连接我们的数据库和Acegi的桥梁。
UserDetailService的要求也很简单,只需要一个返回org.springframework.security.userdetails.User对象的loadUserByUsername(String userName)方法。
而决定用户能否访问指定Web资源的,是RoleVoter类,无需任何修改它可以工作得很好,唯一的缺点是它只认ROLE_前缀,所以搞得白衣的Authority看起来都象角色,不伦不类。
一个自定义的filter,必须包含
authenticationManager,accessDecisionManager,securityMetadataSource三个属性,(https://wenku.baidu.com/view/4ec7e324ccbff121dd368364.html(用户-角色-权限-资源))。
authenticationManager 认证管理器,验证配置,是实现用户认证的入口,主要实现UserDetailService接口。 用户拥有的角色
accessDecisionManager访问决策器,觉定某个用户具有的角色。用户是否拥有所请求资源的权限
securityMetadataSource资源元源数据定义,将所有的资源和权限对应关系建立起来,即定义某一资源可以被哪些角色访问。资源与角色的对应关系
总是根据他们定义的顺序进行执行。因此很重要的是,把更确定的模式定义到列表的上面。
(下例:http://www.importnew.com/20612.html;)
上面的
<!--访问/**资源的用户必须具有ROLE_USER的权限 -->
意思是:要有ROLE_USER这个角色,所拥有的权限。(对他的理解很关键。)这里的“ROLE_USER”表示请求的用户应当具有ROLE_USER角色。“ROLE_”前缀是一个提示Spring使用基于角色的检查的标记。
<intercept-url pattern="/**" access="ROLE_USER" />
access属性表示在请求对应的URL时需要什么权限,默认配置时它应该是一个以逗号分隔的角色列表,请求的用户只需拥有其中的一个角色就能成功访问对应的URL。这里的“ROLE_USER”表示请求的用户应当具有ROLE_USER角色。“ROLE_”前缀是一个提示Spring使用基于角色的检查的标记。
虽然也许看上去没什么问题,但其实存在一定的隐患。因为能访问页面的不是因为他是user角色,而是因为他有“访问”的权限。
可以通过access属性来指定intercept-url对应URL访问所应当具有的权限。access的值是一个字符串,其可以直接是一个权限的定义,也可以是一个表达式。常用的类型有简单的角色名称定义,多个名称之间用逗号分隔,如:
<security:intercept-url pattern="/secure/**" access="ROLE_USER,ROLE_ADMIN"/>
在上述配置中就表示secure路径下的所有URL请求都应当具有ROLE_USER或ROLE_ADMIN权限。当access的值是以“ROLE_”开头的则将会交由RoleVoter进行处理。
其还可以是一个表达式,access="hasAnyRole('ROLE_USER','ROLE_ADMIN'),access="hasRole('ROLE_USER') or hasRole('ROLE_ADMIN')需要注意的是使用表达式时需要指定http元素的use-expressions=”true”。
登录页面不过滤“/login.jsp”放行。通过指定“/login.jsp”的访问权限为“IS_AUTHENTICATED_ANONYMOUSLY”或“ROLE_ANONYMOUS”可以达到,也可以通过指定一个http元素的安全性为none来达到相同的效果<security:http security="none" pattern="/login.jsp" />
spring security 配置文件引入其命名空间,可以简化开发,但是,根据引spring security版本(3,或4)入命名空间,其标签有所不同,
例如:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
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.1.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.1.xsd">
<security:http auto-config="true">
<security:intercept-url pattern="/**" access="ROLE_USER"/>
</security:http>
</beans>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:s="http://www.springframework.org/schema/security"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.2.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-4.1.xsd"
default-autowire="byType" default-lazy-init="false">
<s:http auto-config="true" access-decision-manager-ref="accessDecisionManager" use-expressions="false" disable-url-rewriting="false">
<s:csrf disabled="true" />
<s:headers disabled="true"/>
<s:intercept-url pattern="/**" access="IS_AUTHENTICATED_FULLY"/>
<s:custom-filter before="FILTER_SECURITY_INTERCEPTOR" ref="filterSecurityInterceptor"/>
<s:form-login login-page="/index.jsp" default-target-url="/index.jsp" authentication-failure-url="/index.jsp?error=true" username-parameter="j_username" password-parameter="j_password" login-processing-url="/j_spring_security_check" />
<s:logout logout-success-url="/"/>
<s:remember-me key="e37f4b31-0c45-11dd-bd0b-0800200c9a66"/>
<s:session-management invalid-session-url="/index.jsp" />
</s:http>
</beans>
security:只是我们使用命名空间的一个前缀。还有其他依据版本的不同标签有所不同,但是他们配置的属性基本都有,<sec:http auto-config="true" access-decision-manager-ref="accessDecisionManager"> </sec:http>
基本上我们的配置都在http标签中配置,而其他bean是,对http中ref引用的实类化。
接下来我们还需要在web.xml中定义一个filter用来拦截需要交给Spring Security处理的请求,需要注意的是该filter一定要定义在其它如SpringMVC等拦截请求之前。
<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的决策管理器,其接口为AccessDecisionManager,抽象类为AbstractAccessDecisionManager。而我们要自定义决策管理器的话一般是继承抽象类而不去直接实现接口。
在Spring中引入了投票器(AccessDecisionVoter)的概念,有无权限访问的最终觉得权是由投票器来决定的,最常见的投票器为RoleVoter,在RoleVoter中定义了权限的前缀,
在SpringSecurity中自定义权限前缀,权限的前缀默认是ROLE_,网上的很多例子是说,直接在配置文件中加上下面的配置就可以了。
<bean id="roleVoter" class="org.springframework.security.access.vote.RoleVoter"> <property name="rolePrefix" value="AUTH_"></property> </bean>
但是不管用,而是在我们配置了http auto-config="true"Spring就已经将AccessDecisionManager初始化好了,即便配置到之前也不行,因为这个初始化是Spring自己来完成的,它并没有把你配置的roleVoter注入到AccessDecisionManager中。那我们就来手动的注入AccessDecisionManager。<sec:http auto-config="true" access-decision-manager-ref="accessDecisionManager">写
accessDecisionManager 配置
<bean id="accessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased"><constructor-arg name="decisionVoters">
<list>
<bean class="org.springframework.security.access.vote.RoleVoter">
<property name="rolePrefix" value="" /> 设定权限没有前缀
</bean>
<bean class="org.springframework.security.access.vote.AuthenticatedVoter" /> 使用spring security提供决策管理器AuthenticatedVoter
</list>
</constructor-arg>
</bean>
authenticatedVoter是为了支持IS_AUTHENTICATED这种认证,authenticatedVoter提供的3种认证,分别是 IS_AUTHENTICATED_ANONYMOUSLY 允许匿名用户进入 IS_AUTHENTICATED_FULLY 允许登录用户进入
IS_AUTHENTICATED_REMEMBERED 允许登录用户和rememberMe用户进入
配置很简单,要想修改权限的前缀只需要修改roleVoter中的rolePrefix就可以了,如果不要前缀就让它为“”。
(http://blog.csdn.net/jaune161/article/details/18401233;)
区分Authentication(验证)与 Authorization(授权):验证:这个用户是谁? 用户身份可靠吗?
授权:某用户A是否可以对资源R执行M操作
登录流程(http://blog.csdn.net/rongku/article/details/51235694;)
1)容器启动(MySecurityMetadataSource:loadResourceDefine加载系统资源与权限列表)
2)用户发出请求
3)过滤器拦截(MySecurityFilter:doFilter)
4)取得请求资源所需权限(MySecurityMetadataSource:getAttributes)
5)匹配用户拥有权限和请求权限(MyAccessDecisionManager:decide),如果用户没有相应的权限,
执行第6步,否则执行第7步。
6)登录
7)验证并授权(MyUserDetailServiceImpl:loadUserByUsername)
简化后的认证过程分为7步(http://blog.csdn.net/honghailiang888/article/details/52679264)
-
用户访问网站,打开了一个链接(origin url)。
-
请求发送给服务器,服务器判断用户请求了受保护的资源。
-
由于用户没有登录,服务器重定向到登录页面
-
填写表单,点击登录
-
浏览器将用户名密码以表单形式发送给服务器
-
服务器验证用户名密码。成功,进入到下一步。否则要求用户重新认证(第三步)
-
服务器对用户拥有的权限(角色)判定: 有权限,重定向到origin url; 权限不足,返回状态码403("forbidden").
从第3步,我们可以知道,用户的请求被中断了。用户登录成功后(第7步),会被重定向到origin url,spring security通过使用缓存的request,使得被中断的请求能够继续执行。
Spring Security允许我们定义多个http元素以满足针对不同的pattern请求使用不同的filter链。http中的属性基本都可以自动以处理类,自定义类要继承或实现原来的类,在配置的时候对应属性值引用写自定义的类。如http://elim.iteye.com/blog/2154714
在配置Action与权限时,一定要在Action的路径前面加“/”斜杠,否则,Spring Security就会对该请求的URL熟视无睹,无视它的存在,即使你在Action的前后加上*号进行匹配也不会起任何作用,
spring security默认的权限名字要以ROLE_开头的,注:自定义的权限的命名必须以ROLE_ 开头(不要认为是角色),例如ROLE_USER_CREATE等。(也可以经过配置将ROLE_前缀去掉。)
现在先大概过一遍整个流程,用户登陆,会被AuthenticationProcessingFilter拦截,调用AuthenticationManager的实现,而且AuthenticationManager会调用ProviderManager来获取用户验证信息(不同的Provider调用的服务不同,因为这些信息可以是在数据库上,可以是在LDAP服务器上,可以是xml配置文件上等), 如果验证通过后会将用户的权限信息封装一个User放到spring的全局缓存SecurityContextHolder中 ,以备后面访问资源时使用。访问资源(即授权管理),访问url时,会通过AbstractSecurityInterceptor拦截器拦截,其中会调用FilterInvocationSecurityMetadataSource的方法来获取被拦截url所需的全部权限,在调用授权管理器AccessDecisionManager,这个授权管理器会通过spring的全局缓存SecurityContextHolder获取用户的权限信息,还会获取被拦截的url和被拦截url所需的全部权限,然后根据所配的策略(有:一票决定,一票否定,少数服从多数等),如果权限足够,则返回,权限不够则报错并调用权限不足页面。
首先,是用户在登录页面,进行登录:通过CustomUserService拿到用户的信息后,authenticationManager对比用户的密码(即验证用户),然后这个AuthenticationProcessingFilter拦截器就过咯。
MyUserDetailService这个类负责的是只是获取登陆用户的详细信息(包括密码、角色等),不负责和前端传过来的密码对比,只需返回User对象,后会有其他类根据User对象对比密码的正确性(框架帮我们做)。
public class CustomUserService implements UserDetailsService { //自定义UserDetailsService 接口 @Autowired UserDao userDao; @Autowired PermissionDao permissionDao; public UserDetails loadUserByUsername(String username) { SysUser user = userDao.findByUserName(username); if (user != null) { List<Permission> permissions = permissionDao.findByAdminUserId(user.getId()); List<GrantedAuthority> grantedAuthorities = new ArrayList <>(); for (Permission permission : permissions) { if (permission != null && permission.getName()!=null) { GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(permission.getName()); //1:此处将权限信息添加到 GrantedAuthority 对象中,在后面进行全权限验证时会使用GrantedAuthority 对象。 grantedAuthorities.add(grantedAuthority); } } return new User(user.getUsername(), user.getPassword(), grantedAuthorities); } else { throw new UsernameNotFoundException("admin: " + username + " do not exist!"); } } }
非常重要:此处如果加载的是权限则下面资源元数据,加载的应该是url-权限 到映射map,若此处存入的是角色则下面应该存入url-角色到映射map。因为在决策管理器中要比较映射map中是否有本用户的权限(或角色)
其次,登陆后,每次访问资源url都会被拦截器拦截,会调用getAttributes获取被拦截url所需的权限:
(在此方法中可以自己写正则表达式过滤url了,)
其中loadResourceDefine方法不是必须的,这个只是加载所有的资源与权限(或者角色)的对应关系并缓存起来,避免每次获取权限都访问数据库(提高性能),然后getAttributes根据参数(被拦截url)返回权限集合。
因为loadResourceDefine方法是放在构造器上调用的,而这个类的实例化只在web服务器启动时调用一次,那就是说loadResourceDefine方法只会调用一次,如果资源和权限的对应关系在启动后发生了改变,那么缓存起来的就是脏数据,而笔者这里使用的就是缓存数据,那就会授权错误了。。但如果资源和权限对应关系是不会改变的(都需要重启系统才能时资源配置生效),这种方法性能会好很多。解决方案是,如果数据库中的资源出现的变化,需要刷新内存中已加载的资源信息时,使用下面代码(类名自己改):每次授权修改数据库后强制重新加载资源
- ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(request.getSession().getServletContext());
- CustomInvocationSecurityMetadataSource cs=(CustomInvocationSecurityMetadataSource)ctx.getBean("customSecurityMetadataSource",com.zst.meadow.security.CustomInvocationSecurityMetadataSource.class);
- cs.loadResourceDefine();
如果数据库加载所有url和权限的对应关系,想一下spring的机制(依赖注入),dao需要依赖注入吧,但这是在启动时候,那个dao可能都还没加载,所以这里需要读者自己写sessionFactory,自己写hql或sql,对,就在loadResourceDefine方法里面写(这个应该会写吧,基础来的)。
那如果说想用第二种方法呢(就是允许资源和权限的对应关系改变的那个),那更加简单,根本不需要loadResourceDefine方法了,直接在getAttributes方法里面调用dao(这个是加载完,后来才会调用的,所以可以使用dao),通过被拦截url获取数据库中的所有权限,封装Collection<ConfigAttribute>返回就行了。可以在第一次调用getAttributes方法时再加载(用静态变量缓存起来,第二次就不用再加载了, 注:其实这样不是很严谨,不过笔者这里的对应关系是不变的,单例性不需很强,更严谨的请参考笔者另一篇博文设计模式之单件模式)。。
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的对应关系缓存起来
private
static
List<RoleUrlResource> rus =
null
;
//参数是要访问的url,返回这个url对于的所有权限(或角色)
public
Collection<ConfigAttribute> getAttributes(Object object)
throws
IllegalArgumentException {
// 将参数转为url
String url = ((FilterInvocation)object).getRequestUrl();
//查询所有的url和角色的对应关系
if(rus == null){
rus = roleUrlDao.findAll();
}
/*
Iterator<String>ite = resourceMap.keySet().iterator();
while
(ite.hasNext()) {
String resURL = ite.next();
if
(urlMatcher.pathMatchesUrl(resURL, url)) {
return
resourceMap.get(resURL);
}
}
return
null
; */
//匹配所有的url,并对角色去重 (因为多个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;
}
public
boolean
supports(Class<?>clazz) {
return
true
;
}
public
Collection<ConfigAttribute> getAllConfigAttributes() {
return
null
;
}
}
Spring提供了3个决策管理器
AffirmativeBased 一票通过,只要有一个投票器通过就允许访问
ConsensusBased 有一半以上投票器通过才允许访问资源
UnanimousBased 所有投票器都通过才允许访问
最常见的投票器为RoleVoter,在RoleVoter中定义了权限的前缀(默认ROLE_),弄完这一切就会执行下一个拦截器。
下面的例子没有使用投票器,而是一个简单的自定义决策管理器。如果不存在对该资源的定义,直接放行;否则,如果找到正确的角色,即认为拥有权限,并放行,否则throw new AccessDeniedException("no right");这样,就会进入上面提到的403.jsp页面。(MyAccessDecisionManager)
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();
//ga 为用户所被赋予的权限。 needRole 为访问相应的资源应该具有的权限。
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
;
}
}
上面的投票器,decide方法里面写的就是授权策略了,笔者的实现是,没有明说需要权限的(即没有对应的权限的资源),可以访问,用户具有其中一个或多个以上的权限的可以访问。这个就看需求了,需要什么策略,读者可以自己写其中的策略逻辑。通过就返回,不通过抛异常就行了,spring security会自动跳到权限不足页面(配置文件上配的)。
正常情况开发我们就不用自定义的AccessDecisionManager决策器,直接用Spring的AffirmativeBased,因为Spring本身提供的这些决策管理器就已经很强大了(配置在上面,本文的前半部分面)。。
在程序的任何地方,通过如下方式我们可以获取到当前用户的用户名,SecurityContextHolder.getContext().getAuthentication().getName();
http元素下的form-login元素是用来定义表单登录信息的。当我们什么属性都不指定的时候Spring Security会为我们生成一个默认的登录页面。如果不想使用默认的登录页面,我们可以指定自己的登录页面。
login.jsp:username-parameter:表示登录时用户名使用的是哪个参数,默认是“j_username”。password-parameter:表示登录时密码使用的是哪个参数,默认是“j_password”。login-processing-url:表示登录时提交的地址,默认是“/j-spring-security-check”。这个只是Spring Security用来标记登录页面使用的提交地址,真正关于登录这个请求是不需要用户自己处理的。
j_spring_security_check是一个默认的登录页面并且是在DefaultLoginPageGeneratingFilter中命名的,我们可以使用配置属性login-processing-url来修改这个登录的名字从而使它对我们应用来说是唯一的。
建议修改登录页URL的默认值,修改后不仅能够对应用或搜索引擎更友好,而且能够隐藏你使用Spring Security做为安全实现的事实。
- * 对应的参数名默认为j_username和j_password。
- * 如果不想使用默认的参数名,可以通过UsernamePasswordAuthenticationFilter的usernameParameter和passwordParameter进行指定。
- * 表单的提交路径默认是“j_spring_security_check”,可以通过UsernamePasswordAuthenticationFilter的filterProcessesUrl进行指定。
- * 通过属性postOnly可以指定只允许登录表单进行post请求,默认是true。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
<%
@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:可以使用spring security的标签
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
<%
@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"
/>!
<a href=
"admin.jsp"
>进入admin页面</a>
<a href=
"other.jsp"
>进入其它页面</a>
</body>
</html>
|
数据库设计:https://www.cnblogs.com/softidea/p/7068149.html;http://blog.csdn.net/woshisap/article/details/7250428;https://wenku.baidu.com/view/4ec7e324ccbff121dd368364.html
以将一个url定义一个权限) 和 用户-角色-权限(如crud)-资源 (具体url或方法) 两种
(http://blog.csdn.net/woshisap/article/details/7250428;有数据库以及数据)
Spring Security的优缺点
不可否认,Spring Security依赖于Spring的Ioc、AOP等机制,横切开系统的业务组件,将通用的权限功能注入到业务组件内部,实现了通用功能和业务功能的无缝整合,但又保证了通用功能和业务功能的实现上的分离,省却了一部分工作量,这是其存在的最重要意义。
但又不可否认,Spring Security所具有的缺乏动态资源管理的硬伤(若是能够提供用户、角色、权限和资源的数据库管理,并且提供管理界面那实在是太完美了,可惜这两样一样都不能实现),又令国人用户爱恨交加。
补充:http://www.iteye.com/blogs/subjects/spring_security
核心类简介
1.1 Authentication
Authentication是一个接口,用来表示用户认证信息的,在用户登录认证之前相关信息会封装为一个Authentication具体实现类的对象,在登录认证成功之后又会生成一个信息更全面,包含用户权限等信息的Authentication对象,然后把它保存在SecurityContextHolder所持有的SecurityContext中,供后续的程序进行调用,如访问权限的鉴定等。
1.2 SecurityContextHolder
1.3 AuthenticationManager和AuthenticationProvider
AuthenticationManager是一个用来处理认证(Authentication)请求的接口。在其中只定义了一个方法authenticate(),该方法只接收一个代表认证请求的Authentication对象作为参数,如果认证成功,则会返回一个封装了当前用户权限等信息的Authentication对象进行返回。
1.4 UserDetailsService
登录认证的时候Spring Security会通过UserDetailsService的loadUserByUsername()方法获取对应的UserDetails进行认证,认证通过后会将该UserDetails赋给认证通过的Authentication的principal,然后再把该Authentication存入到SecurityContext中。之后如果需要使用用户信息的时候就是通过SecurityContextHolder获取存放在SecurityContext中的Authentication的principal。
1.5 GrantedAuthority
Authentication的getAuthorities()可以返回当前Authentication对象拥有的权限,即当前用户拥有的权限。其返回值是一个GrantedAuthority类型的数组,每一个GrantedAuthority对象代表赋予给当前用户的一种权限。GrantedAuthority是一个接口,其通常是通过UserDetailsService进行加载,然后赋予给UserDetails的。
GrantedAuthority中只定义了一个getAuthority()方法,该方法返回一个字符串,表示对应权限的字符串表示,如果对应权限不能用字符串表示,则应当返回null。
Spring Security针对GrantedAuthority有一个简单实现SimpleGrantedAuthority。该类只是简单的接收一个表示权限的字符串。Spring Security内部的所有AuthenticationProvider都是使用SimpleGrantedAuthority来封装Authentication对象。
1.1 认证过程
1、用户使用用户名和密码进行登录。
2、Spring Security将获取到的用户名和密码封装成一个实现了Authentication接口的UsernamePasswordAuthenticationToken。
3、将上述产生的token对象传递给AuthenticationManager进行登录认证。
4、AuthenticationManager认证成功后将会返回一个封装了用户权限等信息的Authentication对象。
5、通过调用SecurityContextHolder.getContext().setAuthentication(...)将AuthenticationManager返回的Authentication对象赋予给当前的SecurityContext。
上述介绍的就是Spring Security的认证过程。在认证成功后,用户就可以继续操作去访问其它受保护的资源了,但是在访问的时候将会使用保存在SecurityContext中的Authentication对象进行相关的权限鉴定。
1.2 Web应用的认证过程
如果用户直接访问登录页面,那么认证过程跟上节描述的基本一致,只是在认证完成后将跳转到指定的成功页面,默认是应用的根路径。如果用户直接访问一个受保护的资源,那么认证过程将如下:
1、引导用户进行登录,通常是重定向到一个基于form表单进行登录的页面,具体视配置而定。
2、用户输入用户名和密码后请求认证,后台还是会像上节描述的那样获取用户名和密码封装成一个UsernamePasswordAuthenticationToken对象,然后把它传递给AuthenticationManager进行认证。
3、如果认证失败将继续执行步骤1,如果认证成功则会保存返回的Authentication到SecurityContext,然后默认会将用户重定向到之前访问的页面。
4、用户登录认证成功后再次访问之前受保护的资源时就会对用户进行权限鉴定,如不存在对应的访问权限,则会返回403错误码。
在上述步骤中将有很多不同的类参与,但其中主要的参与者是ExceptionTranslationFilter。
1.2.1 ExceptionTranslationFilter
ExceptionTranslationFilter是用来处理来自AbstractSecurityInterceptor抛出的AuthenticationException和AccessDeniedException的。AbstractSecurityInterceptor是Spring Security用于拦截请求进行权限鉴定的,其拥有两个具体的子类,拦截方法调用的MethodSecurityInterceptor和拦截URL请求的FilterSecurityInterceptor。当ExceptionTranslationFilter捕获到的是AuthenticationException时将调用AuthenticationEntryPoint引导用户进行登录;如果捕获的是AccessDeniedException,但是用户还没有通过认证,则调用AuthenticationEntryPoint引导用户进行登录认证,否则将返回一个表示不存在对应权限的403错误码。
1.2.2 在request之间共享SecurityContext
可能你早就有这么一个疑问了,既然SecurityContext是存放在ThreadLocal中的,而且在每次权限鉴定的时候都是从ThreadLocal中获取SecurityContext中对应的Authentication所拥有的权限,并且不同的request是不同的线程,为什么每次都可以从ThreadLocal中获取到当前用户对应的SecurityContext呢?在Web应用中这是通过SecurityContextPersistentFilter实现的,默认情况下其会在每次请求开始的时候从session中获取SecurityContext,然后把它设置给SecurityContextHolder,在请求结束后又会将SecurityContextHolder所持有的SecurityContext保存在session中,并且清除SecurityContextHolder所持有的SecurityContext。这样当我们第一次访问系统的时候,SecurityContextHolder所持有的SecurityContext肯定是空的,待我们登录成功后,SecurityContextHolder所持有的SecurityContext就不是空的了,且包含有认证成功的Authentication对象,待请求结束后我们就会将SecurityContext存在session中,等到下次请求的时候就可以从session中获取到该SecurityContext并把它赋予给SecurityContextHolder了,由于SecurityContextHolder已经持有认证过的Authentication对象了,所以下次访问的时候也就不再需要进行登录认证了。
权限鉴定
Spring Security是通过拦截器来控制受保护对象的访问的,。在正式访问受保护对象之前,Spring Security将使用AccessDecisionManager来鉴定当前用户是否有访问对应受保护对象的权限。具体来说是由其中的decide()方法负责 。
AccessDecisionManager是由AbstractSecurityInterceptor调用的,它负责鉴定用户是否有访问对应资源(方法或URL)的权限。AccessDecisionManager是一个接口,其中只定义了三个方法:decide()方法用于决定authentication是否符合受保护对象要求的configAttributes。supports(ConfigAttribute attribute)方法是用来判断AccessDecisionManager是否能够处理对应的ConfigAttribute的。supports(Class<?> clazz)方法用于判断配置的AccessDecisionManager是否支持对应的受保护对象类型。
Spring Security已经内置了几个基于投票的AccessDecisionManager(他的3改 实现类,它们分别是AffirmativeBased、ConsensusBased和UnanimousBased。),当然如果需要你也可以实现自己的AccessDecisionManager。
使用这种方式,一系列的AccessDecisionVoter将会被AccessDecisionManager用来对Authentication是否有权访问受保护对象进行投票,然后再根据投票结果来决定是否要抛出
角色的继承
Spring Security为我们提供了一种更为简便的办法,那就是角色的继承,它允许我们的ROLE_ADMIN直接继承ROLE_USER,这样所有ROLE_USER可以访问的资源ROLE_ADMIN也可以访问。
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
标签中各属性对应的优先级,
access具有最高的优先级,各属性对应的优先级
如果没有指定access属性,那么url属性将具有最高优先级,
如果access和url都没有指定,那么将使用第三类属性来鉴定当前用户的权限。当第三类里面同时指定了多个属性时,它们将都发生效果,即必须指定的三类权限都满足才认为是有对应的权限。
对Acl的支持
Acl的全称是Access Control List,俗称访问控制列表,是用以控制对象的访问权限的。其主要思想是将某个对象的某种权限授予给某个用户,或某种GrantedAuthority(可以简单的理解为某种角色),它们之间的关系都是多对多。如果某一个对象的某一操作是受保护的,那么在对该对象进行某种操作时就需要有对应的权限。
整合Cas
Cas是对单点登录的一种实现。