MyEclipse中Spring Security 3.0.3无废话配置(第二章)。
上回说到数据库验证首先看以往的验证配置:
<authentication-manager> <authentication-provider> <user-service> <user name="jimi" password="jimispassword" authorities="ROLE_USER, ROLE_ADMIN" /> <user name="bob" password="bobspassword" authorities="ROLE_USER" /> </user-service> </authentication-provider> </authentication-manager>
现在我们要用数据库来验证。
第一步:建立数据库
这里只用两张表即可解决问题,也许你看过很多建立五张到七张的,那是实际在项目中才这样做,我们做试验的话在这里只建立两张表:1.用户表2.资源表。
用户表四个字段:ID、USERNAME、PASSWORD、ROLE(权限)
资源表有三个字段:ID、资源路径、访问权限
资源路径就是一个URL地址,访问权限就是这个资源需要什么样子的权限才能访问。
第二步:权限提供
package cn.com.fri.security.supports;import java.util.ArrayList;import java.util.Collection;import java.util.HashMap;import java.util.Iterator;import java.util.List;import java.util.Map;import javax.annotation.PostConstruct;import org.springframework.beans.factory.annotation.Autowired;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 org.springframework.security.web.util.AntUrlPathMatcher;import org.springframework.security.web.util.UrlMatcher;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;import cn.com.fri.security.dao.interfaces.IAuthoritiesDAO;import cn.com.fri.security.dao.interfaces.IResourcesDAO;import cn.com.fri.security.vo.Authorities;import cn.com.fri.security.vo.Resources;/** * * 此类在初始化时,应该取到所有资源及其对应角色的定义 * */@Service("securityMetadataSource")public class MyInvocationSecurityMetadataSource implementsFilterInvocationSecurityMetadataSource {@Autowiredprivate IAuthoritiesDAO authoritiesDAO;@Autowiredprivate IResourcesDAO resourcesDAO;private UrlMatcher urlMatcher = new AntUrlPathMatcher();;private static Map<String, Collection<ConfigAttribute>> resourceMap = null;public MyInvocationSecurityMetadataSource() {}/** * 在此处,将数据库中所有的资源以及对应的权限加入到内存中 */@PostConstructpublic void loadResourceDefine() {resourceMap = new HashMap<String, Collection<ConfigAttribute>>();Collection<ConfigAttribute> atts = new ArrayList<ConfigAttribute>();System.out.println(authoritiesDAO);List<Authorities> auth = authoritiesDAO.findAll();for (Authorities au : auth) {ConfigAttribute ca = new SecurityConfig(au.getAuthorname());atts.add(ca);}List<Resources> res = resourcesDAO.findAll();for (Resources resources : res) {resourceMap.put(resources.getResourcesstring(), atts);}System.out.println("权限加载完毕");}// According to a URL, Find out permission configuration of this URL.@Transactional(readOnly = true)public Collection<ConfigAttribute> getAttributes(Object object)throws IllegalArgumentException {// guess object is a URL.String url = ((FilterInvocation) object).getRequestUrl();Iterator<String> ite = resourceMap.keySet().iterator();while (ite.hasNext()) {String resURL = ite.next();if (urlMatcher.pathMatchesUrl(url, resURL)) {return resourceMap.get(resURL);}}return null;}public boolean supports(Class<?> clazz) {return true;}public Collection<ConfigAttribute> getAllConfigAttributes() {return null;}public void setAuthoritiesDAO(IAuthoritiesDAO authoritiesDAO) {this.authoritiesDAO = authoritiesDAO;}public void setResourcesDAO(IResourcesDAO resourcesDAO) {this.resourcesDAO = resourcesDAO;}}找个类的作用就是在服务器启动的时候将存放在数据库表中的资源与访问权限加载到内存中去。
第三步:编写路径验证类:
package cn.com.fri.security.supports;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;import org.springframework.stereotype.Service;@Service("myAccessDecisionManagerBean")public class MyAccessDecisionManager implements AccessDecisionManager {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())) { // ga is user's role.return;}}}throw new AccessDeniedException("no right");}public boolean supports(ConfigAttribute attribute) {// TODO Auto-generated method stubreturn true;}public boolean supports(Class<?> clazz) {return true;}}此类用户判断当前登录的用户是否有权限访问某个路径。而这里路径的访问权限就是从内存中取出来的。
第四步:登录验证:
package cn.com.fri.security.supports;import java.util.ArrayList;import java.util.Collection;import java.util.List;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.dao.DataAccessException;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.core.authority.GrantedAuthorityImpl;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.core.userdetails.UsernameNotFoundException;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;import cn.com.fri.security.dao.interfaces.IUsersDAO;import cn.com.fri.security.vo.Authorities;import cn.com.fri.security.vo.CustomUser;import cn.com.fri.security.vo.Users;@Service("userDetailsService")public class UserDetailsServiceImpl implements UserDetailsService {@Autowiredprivate IUsersDAO dao;@Transactional(readOnly = true)public UserDetails loadUserByUsername(String username)throws UsernameNotFoundException, DataAccessException {System.out.println(username);Users user = dao.findByUsername(username).get(0);//此查询由自己去实现if (user == null)throw new UsernameNotFoundException("user not found");Collection<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();List<Authorities> auths = dao.findAutByUsername(username);//此查询由自己去实现如果用户表中已经包含了权限,就无需进行这一步操作了。System.out.println(">>>>>>获得的权限有:" + auths.size() + "个");for (Authorities a : auths) {GrantedAuthorityImpl authorityImpl = new GrantedAuthorityImpl(a.getAuthorname());authorities.add(authorityImpl);}CustomUser u = new CustomUser(username, user.getPsw(), user.getEnabled(), true, true, true, authorities);u.setUser(user);return u;}}在这个类中,用户一旦登录,我们就能获取到用户的权限,将用户权限放入到CustomUser中,然后返回这个CustomUser即可。CustomUser为继承自org.springframework.security.core.userdetails.User的一个类,继承的原因是为了让原来的User能够拥有更多的属性,例如用户的中文名称等等。里面的List<Authorities> auths = dao.findAutByUsername(username);这一步是因为我是按照七张表的结构去建立的,因此如果你只有两张表,这里完全可以省略。你甚至可以在这里写死(试验用)如下:
GrantedAuthorityImpl authorityImpl = new GrantedAuthorityImpl("ROLE_ADMIN");authorities.add(authorityImpl);第五步:配置SS3的XML文件:
<?xml version="1.0" encoding="UTF-8"?><beans:beans xmlns="http://www.springframework.org/schema/security"xmlns:beans="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.0.xsd"><http auto-config='true' access-denied-page="/common/403.jsp"><!-- 以下为不需要权限就能访问的资源**意思就是包括子目录也同样适用 --><intercept-url pattern="/css/**" filters="none" /><intercept-url pattern="/app/**" filters="none" /><intercept-url pattern="/images/**" filters="none" /><intercept-url pattern="/swf/**" filters="none" /><intercept-url pattern="/login.jsp*" access="IS_AUTHENTICATED_ANONYMOUSLY" /><form-login login-page='/login.jsp' default-target-url="/index.do"always-use-default-target="true" /><!-- 配置退出登录 --><logout invalidate-session="true" logout-url="/logout"logout-success-url="/login.jsp" /><session-management invalid-session-url="/error.html"><concurrency-control max-sessions="1"expired-url="/error.html" /></session-management><!--配置登录验证,包括权限的启动都在这里:--><custom-filter before="FILTER_SECURITY_INTERCEPTOR" ref="myFilter" /></http><!-- 配置登录验证的类 --><beans:bean id="daoAuthenticationProvider"ref="userDetailsService" /></beans:bean><beans:bean id="authenticationManager"/></beans:list></beans:property></beans:bean><authentication-manager><authentication-provider user-service-ref="userDetailsService"></authentication-provider></authentication-manager><!--一个自定义的filter,必须包含authenticationManager,accessDecisionManager,securityMetadataSource三个属性,我们的所有控制将在这三个类中实现,这里使用了annocation,所以ref后面的参数在XML配置中是没有的,详细看这三个类。--><beans:bean id="myFilter"ref="authenticationManager" /><beans:property name="accessDecisionManager" ref="myAccessDecisionManagerBean" /><beans:property name="securityMetadataSource" ref="securityMetadataSource" /></beans:bean></beans:beans>
由于东西较多,所以再一开始的时候建议大家去下载官方的说明文档和中文翻译的说明文档。
在此我们的配置结束。这一部分虽然代码比较多,但是其实一点不复杂,仔仔细细的看一遍就会了。
CustomUser u = new CustomUser(username, user.getPsw(), user .getEnabled(), true, true, true, authorities);
有没有执行到这里。并且里面的authorities是否包含了权限,既:
GrantedAuthorityImpl authorityImpl = new GrantedAuthorityImpl("ROLE_ADMIN"); authorities.add(authorityImpl); 有没有类似的代码?
如果有的话,你的数据库中的资源所需权限是否与之相等,例如:
URL ROLE
/index.jsp ROLE_ADMIN
List<GrantedAuthority> auths = new ArrayList<GrantedAuthority>();//得到用户的权限auths = sysUsersDao.loadUserAuthoritiesByUserName( username );log.info("auths==="+auths);//根据用户名取得一个SysUsers对象,以获取该用户的其他信息。SysUsersBean user = sysUsersDao.findByUserName( username );log.info("user===="+user);return new SysUsers( user.getUserId(), user.getUserAccount(), user.getUserName(), user.getUserPassword(),user.getUserDesc(), user.getEnabled(), user.getIssys(), user.getUserDuty(), user.getUserDept(), new HashSet(0), true, true, true, auths);
如果未登录,直接跳到登陆页面,比如访问/jsp/admin/security/role.jsp,后台输出
INFO : com.sywzsh.security.PawnInvocationSecurityMetadataSourceService - getAttributes(Object)=========/jsp/admin/security/role.jsp
INFO : com.sywzsh.security.PawnAccessDecisionManagerService - decide(Authentication, Object, Collection<ConfigAttribute>) - start
INFO : com.sywzsh.security.PawnAccessDecisionManagerService - 正在访问的url是:FilterInvocation: URL: /jsp/admin/security/role.jsp
INFO : com.sywzsh.security.PawnAccessDecisionManagerService - needRole is:AUTH_XTSZ_USER
INFO : com.sywzsh.security.PawnAccessDecisionManagerService - 授权信息是:ROLE_ANONYMOUS
这个是正确的,但输入用户名和密码后,再点登录,默认应该跳到
default-target-url="/index.jsp"
但后台什么输出都没有,直接又回到登录页面了<!-- 配置登录验证的类 --><beans:bean id="daoAuthenticationProvider"ref="userDetailsService" /></beans:bean><beans:bean id="authenticationManager"/></beans:list></beans:property></beans:bean><authentication-manager><!--------注意这里------------><authentication-provider user-service-ref="userDetailsService"></authentication-provider></authentication-manager>
里面的ref是参考了UserDetailServiceImple的annocation:
@Service("userDetailsService") public class UserDetailsServiceImpl implements UserDetailsService { //..............}public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { // guess object is a URL. String url = ((FilterInvocation) object).getRequestUrl(); log.info("url====="+url); Iterator<String> ite = resourceMap.keySet().iterator(); while (ite.hasNext()) { String resURL = ite.next(); if (urlMatcher.pathMatchesUrl(url, resURL)) { return resourceMap.get(resURL); } } return null; } 其中:log.info("url====="+url);打印出的是/jsp/fail.jsp,这个链接是在security.xml中配置的认证失败跳转页面
<form-login login-page="/jsp/admin/login.jsp" login-processing-url="/topic/add"authentication-failure-url="/jsp/fail.jsp" default-target-url="/jsp/index.jsp" />
点登录按钮,应该判断/jsp/index.jsp是否有权限,不知道为什么直接就跳到失败页面了 6 楼 516816168 2011-08-19 详细,不错啊