如何使用spring security,从简到深有一下几种:1、不用数据库,全部数据写在配置文件,这个也是官方文档里面的demo;2、使用数据库,根据spring security默认实现代码设计数据库,也就是说数据库已经固定了,这种方法不灵活,而且那个数据库设计得很简陋,实用性差;3、spring security和Acegi不同,它不能修改默认filter了,但支持插入filter,所以根据这个,我们可以插入自己的filter来灵活使用;
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.2):
1.Web.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<display-name>Archetype Created Web Application</display-name>
<!-- Spring和mybatis的配置文件 -->
<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>
<!-- 编码过滤器 -->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<async-supported>true</async-supported>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- Spring监听器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 防止Spring内存溢出监听器 -->
<listener>
<listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>
</listener>
<!-- Spring MVC servlet -->
<servlet>
<servlet-name>SpringMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>
<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<!-- 此处可以可以配置成*.do,对应struts的后缀习惯 -->
<url-pattern>/</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>/index.jsp</welcome-file>
</welcome-file-list>
</web-app>
2.securityConfig.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<!-- 自动扫描 -->
<context:component-scan base-package="com.baobaotao" />
<context:property-placeholder location="classpath:redis.properties"
ignore-unresolvable="true" />
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxIdle" value="${redis.maxIdle}" />
<property name="maxActive" value="${redis.maxActive}" />
<property name="maxWait" value="${redis.maxWait}" />
<property name="testOnBorrow" value="${redis.testOnBorrow}" />
</bean>
<bean id="connectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
p:host-name="${redis.host}" p:port="${redis.port}" p:pool-config-ref="poolConfig" />
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="connectionFactory" />
</bean>
<!-- 引入配置文件 -->
<!-- <bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:jdbc.properties" />
<property name="ignoreUnresolvablePlaceholders" value="true" />
</bean> -->
<bean id="propertyConfig" class="com.baobaotao.utils.PropertyPlaceholderConfigurerExt">
<property name="location" value="classpath:jdbc.properties"/>
<property name="ignoreUnresolvablePlaceholders" value="true" />
</bean>
<!-- <context:property-placeholder location="/WEB-INF/proxool.xml"
ignore-unresolvable="true" />
<bean id="dataSourceP">
<property name="driverClassName">
<value>org.logicalcobwebs.proxool.ProxoolDriver</value>
</property>
<property name="url">
<value>proxool.database</value>
</property>
</bean> -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
<!-- spring和MyBatis完美整合,不需要mybatis的配置映射文件 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!-- 自动扫描mapping.xml文件 -->
<property name="mapperLocations" value="classpath:com/baobaotao/mapping/*.xml"></property>
</bean>
<!-- DAO接口所在包名,Spring会自动查找其下的类 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.baobaotao.repository" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>
<!-- (事务管理)transaction manager, use JtaTransactionManager for global tx -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<tx:annotation-driven proxy-target-class="true" />
<!--登录页面不过滤 -->
<security:http pattern="/login.jsp" security="none" />
<security:http>
<security:form-login login-page="/login.jsp" />
<!--访问/admin.jsp资源的用户必须具有ROLE_ADMIN的权限 -->
<!-- <security:intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" /> -->
<!--访问/**资源的用户必须具有ROLE_USER的权限 -->
<security:intercept-url pattern="/**" access="ROLE_USER" />
<security:session-management>
<security:concurrency-control max-sessions="1"
error-if-maximum-exceeded="false" />
</security:session-management>
<!--增加一个filter,这点与 Acegi是不一样的,不能修改默认的filter了, 这个filter位于FILTER_SECURITY_INTERCEPTOR之前 -->
<security:custom-filter ref="myFilter" before="FILTER_SECURITY_INTERCEPTOR" />
</security:http>
<!--一个自定义的filter,必须包含 authenticationManager,accessDecisionManager,securityMetadataSource三个属性,
我们的所有控制将在这三个类中实现,解释详见具体配置 -->
<bean id="myFilter"
class="com.baobaotao.interceptor.MyFilterSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager" />
<property name="accessDecisionManager" ref="myAccessDecisionManagerBean" />
<property name="securityMetadataSource" ref="securityMetadataSource" />
</bean>
<!--验证配置,认证管理器,实现用户认证的入口,主要实现UserDetailsService接口即可 -->
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider user-service-ref="myUserDetailService">
<!--如果用户的密码采用加密的话 <password-encoder hash="md5" /> -->
</security:authentication-provider>
</security:authentication-manager>
<!--在这个类中,你就可以从数据库中读入用户的密码,角色信息,是否锁定,账号是否过期等 -->
<bean id="myUserDetailService" class="com.baobaotao.service.MyUserDetailService" />
<!--访问决策器,决定某个用户具有的角色,是否有足够的权限去访问某个资源 -->
<bean id="myAccessDecisionManagerBean"
class="com.baobaotao.interceptor.MyAccessDecisionManager">
</bean>
<!--资源源数据定义,将所有的资源和权限对应关系建立起来,即定义某一资源可以被哪些角色访问 -->
<bean id="securityMetadataSource"
class="com.baobaotao.interceptor.MyInvocationSecurityMetadataSource" />
</beans>
3.spring-mvc.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:util="http://www.springframework.org/schema/util"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<!-- 自动扫描该包,使SpringMVC认为包下用了@controller注解的类是控制器 -->
<context:component-scan base-package="com.baobaotao.*" />
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<constructor-arg value="UTF-8" />
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<property name="ignoreAcceptHeader" value="false"/>
<property name="defaultContentType" value="text/html"/>
<property name="mediaTypes">
<map>
<entry key="html" value="text/html;charset=UTF-8"/>
<entry key="json" value="application/json;charset=UTF-8"/>
</map>
</property>
<property name="viewResolvers">
<list>
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver" p:order="10"/>
<!--use jsp view resolver-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:order="100">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
</bean>
</list>
</property>
</bean>
<!-- 配置文件上传,如果没有使用文件上传可以不用配置,当然如果不配,那么配置文件中也不必引入上传组件包 -->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 默认编码 -->
<property name="defaultEncoding" value="utf-8" />
<!-- 文件大小最大值 -->
<property name="maxUploadSize" value="10485760000" />
<!-- 内存中的最大值 -->
<property name="maxInMemorySize" value="40960" />
</bean>
</beans>
session-management是用来防止多个用户同时登陆一个账号的。
最重要的是笔者自己写的拦截器myFilter(终于讲到重点了),首先这个拦截器会加载在FILTER_SECURITY_INTERCEPTOR之前(配置文件上有说),最主要的是这个拦截器里面配了三个处理类,第一个是authenticationManager,这个是处理验证的,这里需要特别说明的是:这个类不单只这个拦截器用到,还有验证拦截器AuthenticationProcessingFilter也用到 了,而且实际上的登陆验证也是AuthenticationProcessingFilter拦截器调用authenticationManager来处理的,我们这个拦截器只是为了拿到验证用户信息而已(这里不太清楚,因为authenticationManager笔者设了断点,用户登陆后再也没调用这个类了,而且调用这个类时不是笔者自己写的那个拦截器调用的,看了spring技术内幕这本书才知道是AuthenticationProcessingFilter拦截器调用的)。
securityMetadataSource这个用来加载资源与权限的全部对应关系的,并提供一个通过资源获取所有权限的方法。
accessDecisionManager这个也称为授权器,通过登录用户的权限信息、资源、获取资源所需的权限来根据不同的授权策略来判断用户是否有权限访问资源。
authenticationManager类可以有许多provider(提供者)提供用户验证信息,这里笔者自己写了一个类myUserDetailService来获取用户信息。
MyUserDetailService:
public class MyUserDetailService implements UserDetailsService {
@Autowired
private StudentService studentService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 登陆验证时,通过username获取用户的所有权限信息,
// 并返回User放到spring的全局缓存SecurityContextHolder中,以供授权器使用
Collection<GrantedAuthority> auths = new ArrayList<GrantedAuthority>();
Student student = studentService.findByName(username);
GrantedAuthorityImpl auth1 = new GrantedAuthorityImpl(student.getEnabled());
auths.add(auth1);
User user = new User(username, student.getPsw(), true, true, true, true, auths);
return user;
}
}
其中UserDetailsService接口是spring提供的,必须实现的。别看这个类只有一个方法,而且这么简单,其中内涵玄机。
从数据库中拿到的数据,也就是说ROLE_ADMIN、ROLE_USER、lcy(第一个是登陆账号)、lcy(第二个是密码)是从数据库拿出来的,这个不难实现吧,如果需要数据库时,读者可以用自己写的dao通过参数username来查询出这个用户的权限信息(或是角色信息,就是那个ROLE_*,对必须是ROLE_开头的,不然spring security不认账的,其实是spring security里面做了一个判断,必须要ROLE_开头,读者可以百度改一下),再返回spring自带的数据模型User即可。
这个写应该比较清晰、灵活吧,总之数据读者们通过什么方法获取都行,只要返回一个User对象就行了。(这也是笔者为什么要重写这个类的原因)
通过MyUserDetailService拿到用户信息后,authenticationManager对比用户的密码(即验证用户),然后这个AuthenticationProcessingFilter拦截器就过咯。
下面要说的是另外一个拦截器,就是笔者自己写的拦截器MyFilterSecurityInterceptor:
public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
// 配置文件注入
private FilterInvocationSecurityMetadataSource securityMetadataSource;
// 登陆后,每次访问资源都通过这个拦截器拦截
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
invoke(fi);
}
@Override
public void init(FilterConfig arg0) throws ServletException {
// TODO Auto-generated method stub
}
@Override
public Class<?> getSecureObjectClass() {
return FilterInvocation.class;
}
@Override
public SecurityMetadataSource obtainSecurityMetadataSource() {
return this.securityMetadataSource;
}
@Override
public void destroy() {
// TODO Auto-generated method stub
}
public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
return securityMetadataSource;
}
public void setSecurityMetadataSource(FilterInvocationSecurityMetadataSource securityMetadataSource) {
this.securityMetadataSource = securityMetadataSource;
}
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);
}
}
}
继承AbstractSecurityInterceptor、实现Filter是必须的。
首先,登陆后,每次访问资源都会被这个拦截器拦截,会执行doFilter这个方法,这个方法调用了invoke方法,其中fi断点显示是一个url(可能重写了toString方法吧,但是里面还有一些方法的),最重要的是beforeInvocation这个方法,它首先会调用MyInvocationSecurityMetadataSource类的getAttributes方法获取被拦截url所需的权限,在调用MyAccessDecisionManager类decide方法判断用户是否够权限。弄完这一切就会执行下一个拦截器。
再看一下这个MyInvocationSecurityMetadataSource的实现:
public class MyInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
private UrlMatcher urlMatcher = new AntUrlPathMatcher();
private static Map<String, Collection<ConfigAttribute>> resourceMap = null;
//将所有的角色和url的对应关系缓存起来
private static List<UrlResource> rus = null;
@Autowired
private RoleUrlBizService roleUrlBizService;
// tomcat启动时实例化一次
public MyInvocationSecurityMetadataSource() {
// loadResourceDefine();
}
/*这种缓存的实现其实有一个缺点,因为loadResourceDefine方法是放在构造器上调用的,
而这个类的实例化只在web服务器启动时调用一次,
那就是说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对于的所有权限(或角色)
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
// 将参数转为url
String url = ((FilterInvocation)object).getRequestUrl();
//查询所有的url和角色的对应关系
if(rus == null||rus.size()==0){
rus = roleUrlBizService.findAll();
}
//匹配所有的url,并对角色去重
Set<String> roles = new HashSet<String>();
for(UrlResource ru : rus){
if (urlMatcher.pathMatchesUrl(ru.getUrl(), url)) {
roles.add(ru.getRole().getEnabled());
}
}
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; */
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean supports(Class<?> clazz) {
// TODO Auto-generated method stub
return true;
}
}
实现FilterInvocationSecurityMetadataSource接口也是必须的。
首先,这里也是模拟了从数据库中获取信息。
其中loadResourceDefine方法不是必须的,这个只是加载所有的资源与权限的对应关系并缓存起来,避免每次获取权限都访问数据库(提高性能),然后getAttributes根据参数(被拦截url)返回权限集合。
这种缓存的实现其实有一个缺点,因为loadResourceDefine方法是放在构造器上调用的,而这个类的实例化只在web服务器启动时调用一次,那就是说loadResourceDefine方法只会调用一次,如果资源和权限的对应关系在启动后发生了改变,那么缓存起来的就是脏数据,而笔者这里使用的就是缓存数据,那就会授权错误了。但如果资源和权限对应关系是不会改变的,这种方法性能会好很多。
现在说回有数据库的灵活实现,读者看到这,可能会说,这还不简单,和上面MyUserDetailService类一样使用dao灵活获取数据就行啦。
注意:接口UrlMatcher和实现类AntUrlPathMatcher是笔者自己写的,这本来是spring以前版本有的,现在没有了,但是觉得好用就用会来了,直接上代码(读者也可以自己写正则表达式验证被拦截url和缓存或数据库的url是否匹配):
public interface UrlMatcher{
Object compile(String paramString);
boolean pathMatchesUrl(Object paramObject, String paramString);
String getUniversalMatchPattern();
boolean requiresLowerCaseUrl();
}
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类的实现:
public class MyAccessDecisionManager implements AccessDecisionManager {
// 检查用户是否够权限访问资源
// 参数authentication是从spring的全局缓存SecurityContextHolder中拿到的,里面是用户的权限信息
// 参数object是url
// 参数configAttributes所需的权限
@Override
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");
}
@Override
public boolean supports(ConfigAttribute attribute) {
// TODO Auto-generated method stub
return true;
}
@Override
public boolean supports(Class<?> clazz) {
// TODO Auto-generated method stub
return true;
}
}
接口AccessDecisionManager也是必须实现的。
decide方法里面写的就是授权策略了,笔者的实现是,没有明说需要权限的(即没有对应的权限的资源),可以访问,用户具有其中一个或多个以上的权限的可以访问。这个就看需求了,需要什么策略,读者可以自己写其中的策略逻辑。通过就返回,不通过抛异常就行了,spring security会自动跳到权限不足页面(配置文件上配的)。
剩下的页面代码
本来想给这个demo的源码出来的,但是笔者觉得,通过这个教程一步一步读下来,并自己敲一遍代码,会比直接运行一遍demo印象更深刻,并且更容易理解里面的原理。
而且我的源码其实都公布出来了:
login.jsp:
<%@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"%>
<!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>
<%-- <%@ page language="java" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>请上传用户头像</title>
<META HTTP-EQUIV="Refresh" CONTENT="0;URL=userinfo.jsp">
</head>
<body>
<p>Loading ...</p>
</body>
</html> --%>
<%-- <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>请上传用户头像</title>
</head>
<body>
<h1>
请选择上传的头像文件
</h1>
<form method="post" action="<c:url value="/baobaotao/user/uploadPhotoHead"/>" enctype="multipart/form-data">
<input type="text" name="name" value="中国"/>
<input type="file" name="upFile" />
<input name="userId" value="1">
<input type="submit" />
</form>
<img src="http://10.101.145.59:8080/20160913/Tulips.jpg"/>
</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>
大体过程如下:
1.网址/index.jsp请求首先MyUserDetailService获取登录验证权限并返回User供授权器使用(封装角色ROLE_USER进User)
2.MyInvocationSecurityMetadataSource通过url获取可访问的所有角色并返回configAttribute
3.AbstractSecurityInterceptor调用beforeInvocation 来决定是否可以访问
4.beforeInvocation 主要是通过accessDecisionManager来判断
表结构 student
url_resource