登录流程
1)容器启动(MySecurityMetadataSource:loadResourceDefine加载系统资源与权限列表)
2)用户发出请求
3)过滤器拦截(MySecurityFilter:doFilter)
4)取得请求资源所需权限(MySecurityMetadataSource:getAttributes)
5)匹配用户拥有权限和请求权限(MyAccessDecisionManager:decide),如果用户没有相应的权限,
执行第6步,否则执行第7步。
6)登录
7)验证并授权(MyUserDetailServiceImpl:loadUserByUsername)
1、web.xml中加入过滤器
<!-- SpringSecurity 核心过滤器配置 --> <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>
<?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.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd"> <!-- entry-point-ref 配置自定义登录 --> <http use-expressions="true" entry-point-ref="authenticationProcessingFilterEntryPoint"> <!-- 登出配置 --> <logout logout-url="/j_spring_security_logout" logout-success-url="/login" /> <access-denied-handler error-page="/noPower" /> <!-- 过滤不被拦截的请求 --> <intercept-url pattern="/login*" access="permitAll" /> <intercept-url pattern="/resources/**" access="permitAll" /> <!-- 只有权限才能访问的请求 --> <intercept-url pattern="/admin/**" access="isAuthenticated()" /> <custom-filter ref="loginFilter" position="FORM_LOGIN_FILTER" /> <custom-filter ref="securityFilter" before="FILTER_SECURITY_INTERCEPTOR" /> </http> <beans:bean id="loginFilter" class="cn.com.abel.test.service.security.MyUsernamePasswordAuthenticationFilter"> <!-- 登录提交处理 --> <beans:property name="filterProcessesUrl" value="/j_spring_security_check"></beans:property> <!-- 登录成功跳转 --> <beans:property name="authenticationSuccessHandler" ref="loginLogAuthenticationSuccessHandler"></beans:property> <!-- 设置登录失败的网址 --> <beans:property name="authenticationFailureHandler" ref="simpleUrlAuthenticationFailureHandler"></beans:property> <!-- 用户拥有权限 --> <beans:property name="authenticationManager" ref="myAuthenticationManager"></beans:property> </beans:bean> <beans:bean id="loginLogAuthenticationSuccessHandler" class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler"> <beans:property name="defaultTargetUrl" value="/admin/index"></beans:property> </beans:bean> <beans:bean id="simpleUrlAuthenticationFailureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler"> <beans:property name="defaultFailureUrl" value="/login"></beans:property> </beans:bean> <authentication-manager alias="myAuthenticationManager"> <authentication-provider user-service-ref="myUserDetailServiceImpl"> <password-encoder ref="encoder" /> </authentication-provider> </authentication-manager> <beans:bean id="myUserDetailServiceImpl" class="cn.com.abel.test.service.security.AdminUserDetailServiceImpl"> </beans:bean> <beans:bean id="authenticationProcessingFilterEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint"> <beans:property name="loginFormUrl" value="/login"></beans:property> </beans:bean> <!-- 认证过滤器 --> <beans:bean id="securityFilter" class="cn.com.abel.test.service.security.MySecurityFilter"> <!-- 用户拥有的角色 --> <beans:property name="authenticationManager" ref="myAuthenticationManager" /> <!-- 用户是否拥有所请求资源的权限 --> <beans:property name="accessDecisionManager" ref="myAccessDecisionManager" /> <!-- 资源与角色的对应关系 --> <beans:property name="securityMetadataSource" ref="mySecurityMetadataSource" /> <!-- <beans:property name="rejectPublicInvocations" value="true"/> --> </beans:bean> <beans:bean id="myAccessDecisionManager" class="myAccessDecisionManager"></beans:bean> <beans:bean id="mySecurityMetadataSource" class="cn.com.abel.test.service.security.MySecurityMetadataSource"> <beans:constructor-arg> <beans:ref bean="resourceService" /> </beans:constructor-arg> </beans:bean> <beans:bean id="encoder" class="org.springframework.security.authentication.encoding.Md5PasswordEncoder"></beans:bean> </beans:beans>
package cn.com.abel.test.service.security; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; public class MyUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter{ @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { if (!request.getMethod().equals("POST")) { throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod()); } String username = obtainUsername(request); String password = obtainPassword(request); if (username == null) { username = ""; } if (password == null) { password = ""; } username = username.trim(); UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); // Allow subclasses to set the "details" property setDetails(request, authRequest); // //登录验证码,如需要开启把下面注释去掉则可 // String authCode = StringUtils.defaultString(request.getParameter("authCode")); // if(!AdwImageCaptchaServlet.validateResponse(request, authCode)){ // throw new AuthenticationServiceException("validCode.auth.fail"); // } return this.getAuthenticationManager().authenticate(authRequest); } }
package cn.com.abel.test.service.security; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import org.springframework.security.access.SecurityMetadataSource; import org.springframework.security.access.intercept.AbstractSecurityInterceptor; import org.springframework.security.access.intercept.InterceptorStatusToken; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; public class MySecurityFilter extends AbstractSecurityInterceptor implements Filter { //与applicationContext-security.xml里的myFilter的属性securityMetadataSource对应, //其他的两个组件,已经在AbstractSecurityInterceptor定义 private FilterInvocationSecurityMetadataSource securityMetadataSource; @Override public SecurityMetadataSource obtainSecurityMetadataSource() { return this.securityMetadataSource; } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { FilterInvocation fi = new FilterInvocation(request, response, chain); invoke(fi); } private void invoke(FilterInvocation fi) throws IOException, ServletException { // object为FilterInvocation对象 //1.获取请求资源的权限 //执行Collection<ConfigAttribute> attributes = SecurityMetadataSource.getAttributes(object); //2.是否拥有权限 //获取安全主体,可以强制转换为UserDetails的实例 //1) UserDetails // Authentication authenticated = authenticateIfRequired(); //this.accessDecisionManager.decide(authenticated, object, attributes); //用户拥有的权限 //2) GrantedAuthority //Collection<GrantedAuthority> authenticated.getAuthorities() //System.out.println("用户发送请求! "); InterceptorStatusToken token = null; token = super.beforeInvocation(fi); try { fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } finally { super.afterInvocation(token, null); } } public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() { return securityMetadataSource; } public void setSecurityMetadataSource(FilterInvocationSecurityMetadataSource securityMetadataSource) { this.securityMetadataSource = securityMetadataSource; } public void init(FilterConfig arg0) throws ServletException { // TODO Auto-generated method stub } public void destroy() { // TODO Auto-generated method stub } @Override public Class<? extends Object> getSecureObjectClass() { //下面的MyAccessDecisionManager的supports方面必须放回true,否则会提醒类型错误 return FilterInvocation.class; } }
package cn.com.abel.test.service.security; import java.util.HashSet; import java.util.List; import java.util.Set; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import cn.com.abel.test.model.RoleModel; import cn.com.abel.test.model.MemberModel; import cn.com.abel.test.service.RoleService; import cn.com.abel.test.service.MemberService; public class AdminUserDetailServiceImpl implements UserDetailsService { @Autowired private MemberService memberService; @Autowired RoleService roleService; //登录验证 public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { MemberModel member = memberService.getUserDetailsByUserName(username); if(member==null){ throw new UsernameNotFoundException("member "+username +" not found."); } Set<GrantedAuthority> grantedAuths = obtionGrantedAuthorities(member); //封装成spring security的user User userdetail = new User(user.getUserName(), user.getPassword(), true, // 账号状态 0 表示停用 1表示启用 true, true, true, grantedAuths // 用户的权限 ); return userdetail; } //取得用户的权限 private Set<GrantedAuthority> obtionGrantedAuthorities(MemberModel member) { Set<GrantedAuthority> authSet = new HashSet<GrantedAuthority>(); List<RoleModel> roles = roleService.getRoleByUser(member)<span style="font-family:Arial, Helvetica, sans-serif;">;</span> if(roles!=null){ for(RoleModel role : roles) { authSet.add(new SimpleGrantedAuthority(role.getRoleCode().trim())); } } return authSet; } }
package cn.com.abel.test.service.security; 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.authentication.InsufficientAuthenticationException; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; public class MyAccessDecisionManager implements AccessDecisionManager { public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException { if(configAttributes == null) { return; } //所请求的资源拥有的权限(一个资源对多个权限) Iterator<ConfigAttribute> iterator = configAttributes.iterator(); while(iterator.hasNext()) { ConfigAttribute configAttribute = iterator.next(); //访问所请求资源所需要的权限 String needPermission = configAttribute.getAttribute(); System.out.println("needPermission is " + needPermission); //用户所拥有的权限authentication for(GrantedAuthority ga : authentication.getAuthorities()) { if(needPermission.equals(ga.getAuthority())) { return; } } } //没有权限让我们去捕捉 throw new AccessDeniedException(" 没有权限访问!"); } public boolean supports(ConfigAttribute attribute) { // TODO Auto-generated method stub return true; } public boolean supports(Class<?> clazz) { // TODO Auto-generated method stub return true; } }
package cn.com.abel.test.service.security; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang.StringUtils; import org.springframework.beans.factory.InitializingBean; 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.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; import cn.com.abel.test.model.RoleModel; import cn.com.abel.test.service.ResourceService; public class MySecurityMetadataSource implements FilterInvocationSecurityMetadataSource,InitializingBean { private static final String AUTH_NO_ROLE =" __AUTH_NO_ROLE__"; private ResourceService resourceService; public MySecurityMetadataSource(ResourceService resourceService) { this.resourceService = resourceService; } private static Map<String, Collection<ConfigAttribute>> resourceMap = null; public Collection<ConfigAttribute> getAllConfigAttributes() { return null; } public boolean supports(Class<?> clazz) { return true; } private void loadResourceDefine() { if(resourceMap == null) { resourceMap = new ConcurrentHashMap<String, Collection<ConfigAttribute>>(); }else{ resourceMap.clear(); } Map<String,List<RoleModel>> resourceRoleMap = resourceService.getAllResourceRole(); for (Entry<String,List<RoleModel>> entry : resourceRoleMap.entrySet()) { String url = entry.getKey(); List<RoleModel> values = entry.getValue(); Collection<ConfigAttribute> configAttributes = new ArrayList<ConfigAttribute>(); for(RoleModel secRoleModel : values){ ConfigAttribute configAttribute = new SecurityConfig(StringUtils.defaultString(secRoleModel.getRoleCode(),AUTH_NO_ROLE)); configAttributes.add(configAttribute); } resourceMap.put(url, configAttributes); } } public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { HttpServletRequest request = ((FilterInvocation) object).getHttpRequest(); TreeMap<String, Collection<ConfigAttribute>> attrMap = new TreeMap<String, Collection<ConfigAttribute>>(resourceMap); Iterator<String> ite = attrMap.keySet().iterator(); RequestMatcher urlMatcher = null; Collection<ConfigAttribute> attrSet = new HashSet<ConfigAttribute>(); //match all of /admin/** a/b/** while (ite.hasNext()) { String resURL = ite.next(); urlMatcher = new AntPathRequestMatcher(resURL); if (urlMatcher.matches(request)||StringUtils.equals(request.getRequestURI(),resURL)) { attrSet.addAll(attrMap.get(resURL)); } } if(!attrSet.isEmpty()){ return attrSet; } return null; } @Override public void afterPropertiesSet() throws Exception { loadResourceDefine() ; } }
此类是为从数据库获取系统中的资源所属的角色,根据自己的数据表自行编写。
package cn.com.abel.test.service; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import org.apache.commons.collections.CollectionUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import cn.com.abel.test.mapper.ResourceModelMapper; import cn.com.abel.test.mapper.RoleModelMapper; import cn.com.abel.test.mapper.RoleResourcetModelMapper; import cn.com.abel.test.model.ResourceModel; import cn.com.abel.test.model.ResourceModelCriteria; import cn.com.abel.test.model.RoleModel; import cn.com.abel.test.model.RoleModelCriteria; import cn.com.abel.test.model.RoleResourcetModel; import cn.com.abel.test.model.RoleResourcetModelCriteria; @Service public class ResourceService { @Autowired ResourceModelMapper resourceModelMapper; @Autowired RoleModelMapper roleMapper; @Autowired RoleResourcetModelMapper roleResMapper; /** * 获取各个资源(url)对应的角色 * @return */ public Map<String,List<RoleModel>> getAllResourceRole(){ Map<String,List<RoleModel>> resultMap = new HashMap<String,List<RoleModel>>(); ResourceModelCriteria secResourceModelExample = new ResourceModelCriteria(); List<ResourceModel> resourceList = resourceModelMapper.selectByExample(secResourceModelExample); if(CollectionUtils.isNotEmpty(resourceList)){ for(ResourceModel secResourceModel : resourceList){ RoleModelCriteria roleCriteria = new RoleModelCriteria(); roleCriteria.createCriteria().andIdIn(getRoleIdsByResourceId(secResourceModel.getId())); List<RoleModel> roleList = roleMapper.selectByExample(roleCriteria); resultMap.put(secResourceModel.getValue(), roleList); } } return resultMap; } public List<Integer> getRoleIdsByResourceId(Integer resourceId){ List<Integer> roleIds = new ArrayList<Integer>(); RoleResourcetModelCriteria criteria = new RoleResourcetModelCriteria(); criteria.createCriteria().andResourceIdEqualTo(resourceId); List<RoleResourcetModel> list = roleResMapper.selectByExample(criteria); if(CollectionUtils.isNotEmpty(list)){ for(RoleResourcetModel model : list){ roleIds.add(model.getRoleId()); } } HashSet<Integer> h = new HashSet<Integer>(roleIds); roleIds.clear(); roleIds.addAll(h); return roleIds; } }
CREATE TABLE `auth_resource` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `name` VARCHAR(100) NULL DEFAULT NULL COMMENT '资源名称', `value` VARCHAR(100) NULL DEFAULT NULL COMMENT '资源值', `summary` VARCHAR(1000) NULL DEFAULT NULL COMMENT '资源描述', `updated_time` DATETIME NULL DEFAULT NULL, `updated_user` VARCHAR(100) NULL DEFAULT NULL, PRIMARY KEY (`id`) ) COMMENT='资源访问表' COLLATE='utf8_general_ci' ENGINE=InnoDB; CREATE TABLE `auth_role` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `role_name` VARCHAR(100) NULL DEFAULT NULL COMMENT '角色名称', `role_code` VARCHAR(100) NULL DEFAULT NULL COMMENT '角色代码', `updated_time` DATETIME NULL DEFAULT NULL, `updated_user` VARCHAR(100) NULL DEFAULT NULL, PRIMARY KEY (`id`) ) COMMENT='角色表' COLLATE='utf8_general_ci' ENGINE=InnoDB; CREATE TABLE `role_resource` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `role_id` INT(11) NOT NULL, `resource_id` INT(11) NOT NULL, PRIMARY KEY (`id`), UNIQUE INDEX `role_id_resource_id` (`role_id`, `resource_id`) ) COMMENT='资源角色关联表' COLLATE='utf8_general_ci' ENGINE=InnoDB; CREATE TABLE `member` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `user_name` VARCHAR(100) NULL DEFAULT NULL, `nick` VARCHAR(100) NULL DEFAULT NULL, `password` VARCHAR(100) NULL DEFAULT NULL, `sex` INT(11) NULL DEFAULT NULL, `birthday` DATE NULL DEFAULT NULL, `mobile` VARCHAR(50) NULL DEFAULT NULL, `email` VARCHAR(50) NULL DEFAULT NULL, `address` VARCHAR(512) NULL DEFAULT NULL, `regip` VARCHAR(100) NULL DEFAULT NULL, `created_time` DATETIME NULL DEFAULT NULL, PRIMARY KEY (`id`) ) COMMENT='用户表' COLLATE='utf8_general_ci' ENGINE=InnoDB AUTO_INCREMENT=2; CREATE TABLE `member_role` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `member_id` INT(11) NOT NULL, `role_id` INT(11) NOT NULL, PRIMARY KEY (`id`), UNIQUE INDEX `member_id_role_id` (`member_id`, `role_id`) ) COMMENT='用户角色关联表' COLLATE='utf8_general_ci' ENGINE=InnoDB;