一、简介
Web应用的安全管理,主要包括两个方面的内容:身份认证、用户授权,此处使用spring-cloud-security来说明。
二、依赖管理
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-security</artifactId> </dependency> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity4</artifactId> </dependency>
三、安全策略配置
Spring Security已经大体实现了,我们这里只是需要一些配置与引用。
package com.example.demo.config; import com.example.demo.utils.security.CustomUserService; import com.example.demo.utils.security.LoginSuccessHandler; import com.example.demo.utils.security.MyFilterSecurityInterceptor; import com.example.demo.utils.security.SecuritySettings; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; /** * Security安全配置 * * @Author: 我爱大金子 * @Description: Security安全配置 * @Date: Create in 15:20 2017/7/5 */ @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private CustomFilterSecurityInterceptor customFilterSecurityInterceptor; // 权限管理过滤器 @Autowired private SecuritySettings securitySettings; // 自定义安全配置类 /**注册UserDetailsService的bean*/ @Bean public UserDetailsService customUserService(){ return new CustomUserService(); } /**登录认证*/ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(customUserService()); //userDetailsService验证 } /***设置不拦截规则*/ @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/js/**", "/css/**", "/images/**", "/druid/**"); } /**安全策略配置*/ @Override protected void configure(HttpSecurity http) throws Exception { // 设置游客可以访问的URI if (StringUtils.isNotBlank(securitySettings.getPermitall())) { http.authorizeRequests().antMatchers(securitySettings.getPermitall().split(",")).permitAll(); } http.authorizeRequests() .anyRequest().authenticated() //任何请求,登录后可以访问 // 配置登录URI、登录失败跳转URI与登录成功后默认跳转URI .and().formLogin().loginPage("/login").failureUrl("/login?error").permitAll().defaultSuccessUrl("/", true).successHandler(loginSuccessHandler()) // 注销行为任意访问 .and().logout().permitAll() // 设置拒绝访问的提示URI .and().exceptionHandling().accessDeniedPage("/login?illegal") ; http.addFilterBefore(myFilterSecurityInterceptor, FilterSecurityInterceptor.class); } /**登录成功处理器*/ private AuthenticationSuccessHandler loginSuccessHandler() { return new LoginSuccessHandler(); } }
说明:
loginPage:设置一个实验自定义的登录URI
loginSuccessHandler:设置自定义的登录处理器
permitAll:是允许访问
accessDeniedPage:配置拒绝访问的提示URI
antMatchers:对URI的配置
假设我要管理员才可以访问admin文件夹下的内容,如:.antMatchers("/admin/**").hasRole("ROLE_ADMIN"),
也可以设置admin文件夹下的文件可以有多个角色来访问,如:.antMatchers("/admin/**").hasAnyRole("ROLE_ADMIN","ROLE_USER")
也可以通过hasIpAddress来指定某一个ip可以访问该资源,写法如下.antMatchers("/admin/**").hasIpAddress("210.210.210.210")
3.1、自定义安全配置类
为是更方便的使用springSecurity,我们自定义一个权限的配置类,如配置登录的URI、游客访问的URI等配置项
package com.example.demo.utils.security; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; /** * 自定义安全配置类 * * @Author: 我爱大金子 * @Description: 自定义安全配置类 * @Date: Create in 9:45 2017/7/6 */ @Configuration @ConfigurationProperties(prefix = "securityConfig") public class SecuritySettings { /**允许访问的URL,多个用逗号分隔*/ private String permitall; public String getPermitall() { return permitall; } public void setPermitall(String permitall) { this.permitall = permitall; } }
3.2、登录成功处理器
登录成功后,如果需要对用户的行为做一些记录或者执行其它操作,则可以使用登录成功处理器。
package com.example.demo.utils.security; import com.example.demo.pojo.SysUser; import org.springframework.security.core.Authentication; import org.springframework.security.core.userdetails.User; import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * 登录成功处理器 * * @Author: 我爱大金子 * @Description: 登录成功处理器 * @Date: Create in 11:35 2017/7/6 */ public class LoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException { User userDetails = (User) authentication.getPrincipal(); System.out.println("登录用户:username=" + userDetails.getUsername() + ", uri=" + request.getContextPath()); super.onAuthenticationSuccess(request, response, authentication); } }
3.3、springMVC 配置(访问 /login 转向 login.html 页面)
package com.example.demo.config; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; /** * springMVC 配置(注册访问 /login 转向 login.html 页面) * * @Author: 我爱大金子 * @Description: springMVC 配置(注册访问 /login 转向 login.html 页面) * @Date: Create in 16:24 2017/7/5 */ @Configuration public class WebMvcConfig extends WebMvcConfigurerAdapter { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/login").setViewName("login"); } }
四、登录认证
在安全策略配置代码中有,主要看自定义的CustomUserService,此类实现了UserDetailsService接口,重写了loadUserByUsername方法
package com.example.demo.utils.security; import com.example.demo.dao.SysPermissionDao; import com.example.demo.dao.SysUserDao; import com.example.demo.pojo.SysPermission; import org.springframework.security.core.userdetails.User; import com.example.demo.pojo.SysUser; 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 java.util.ArrayList; import java.util.List; /** * 自定义UserDetailsService,将用户权限交给springsecurity进行管控 * * @Author: 我爱大金子 * @Description: 将用户权限交给Springsecurity进行管控 * @Date: Create in 16:19 2017/7/5 */ public class CustomUserService implements UserDetailsService { @Autowired private SysUserDao sysUserDao; @Autowired private SysPermissionDao sysPermissionDao; @Override public UserDetails loadUserByUsername(String username) { SysUser user = sysUserDao.findByUserName(username); if (user != null) { List<SysPermission> permissions = sysPermissionDao.findByAdminUserId(user.getId()); List<GrantedAuthority> grantedAuthorities = new ArrayList <>(); for (SysPermission 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!"); } } }
五、权限管理
在Security安全配置类中使用了权限管理过滤器CustomFilterSecurityInterceptor
package com.example.demo.utils.security; 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.beans.factory.annotation.Autowired; 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.stereotype.Service; import java.io.IOException; /** * 权限管理过滤器 * * @Author: 我爱大金子 * @Description: 权限管理过滤器 * @Date: Create in 17:16 2017/7/5 */ @Service public class CustomFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter { @Autowired private CustomFilterInvocationSecurityMetadataSource customFilterInvocationSecurityMetadataSource; // 权限配置资源管理器 /**权限管理决断器*/ @Autowired public void setMyAccessDecisionManager(CustomAccessDecisionManager customAccessDecisionManager) { super.setAccessDecisionManager(customAccessDecisionManager); } @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { FilterInvocation fi = new FilterInvocation(request, response, chain); invoke(fi); } 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); } } @Override public void destroy() { } @Override public Class<?> getSecureObjectClass() { return FilterInvocation.class; } @Override public SecurityMetadataSource obtainSecurityMetadataSource() { return this.customFilterInvocationSecurityMetadataSource; } }
说明:
customFilterSecurityInterceptor:权限管理过滤器
customAccessDecisionManager:权限管理决断器
customFilterInvocationSecurityMetadataSource:权限配置资源管理器
其中过滤器在系统启动时开始工作,并同时导入权限配置资源管理器和权限管理决断器,对用户访问的资源进行管理。权限管理决断器对用户访问的资源与用户拥有的角色权限进行对比,以此来判断用户是否对某个资源具有访问权限。
5.1、权限管理过滤器
继承与AbstractSecurityInterceptor,实时监控用户的行为,防止用户访问未被授权的资源。
package com.example.demo.utils.security; 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.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; 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.stereotype.Service; import java.io.IOException; /** * 权限管理过滤器 * * @Author: 我爱大金子 * @Description: 权限管理过滤器 * @Date: Create in 17:16 2017/7/5 */ @Service public class CustomFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter { Logger log = LoggerFactory.getLogger(CustomFilterSecurityInterceptor.class); @Autowired private CustomFilterInvocationSecurityMetadataSource customFilterInvocationSecurityMetadataSource; // 权限配置资源管理器 /**权限管理决断器*/ @Autowired public void setMyAccessDecisionManager(CustomAccessDecisionManager customAccessDecisionManager) { super.setAccessDecisionManager(customAccessDecisionManager); } @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { FilterInvocation fi = new FilterInvocation(request, response, chain); log.info("【权限管理过滤器】请求URL:" + fi.getRequestUrl()); invoke(fi); } public void invoke(FilterInvocation fi) throws IOException, ServletException { //fi里面有一个被拦截的url //里面调用CustomFilterInvocationSecurityMetadataSource的getAttributes(Object object)这个方法获取fi对应的所有权限 //再调用CustomAccessDecisionManager的decide方法来校验用户的权限是否足够 InterceptorStatusToken token = super.beforeInvocation(fi); try { //执行下一个拦截器 fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } catch(Exception e) { log.error("【权限管理过滤器】【异常】" + e.getMessage(), e); } finally { super.afterInvocation(token, null); } } @Override public void destroy() { } @Override public Class<?> getSecureObjectClass() { return FilterInvocation.class; } @Override public SecurityMetadataSource obtainSecurityMetadataSource() { return this.customFilterInvocationSecurityMetadataSource; } }
5.2、权限管理决断器
权限管理的关键部分就是决断器,它实现了AccessDecisionManager,重写了decide方法,使用自定义的决断器,在用户访问受保护的资源时,决断器判断用户拥有的角色中是否对改资源具有访问权限,如果没有,则拒绝访问
package com.example.demo.utils.security; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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; import org.springframework.stereotype.Service; import java.util.Collection; import java.util.Iterator; /** * 权限管理决断器 * * @Author: 我爱大金子 * @Description: 权限管理决断器 * @Date: Create in 17:15 2017/7/5 */ @Service public class CustomAccessDecisionManager implements AccessDecisionManager { Logger log = LoggerFactory.getLogger(CustomAccessDecisionManager.class); // decide 方法是判定是否拥有权限的决策方法, //authentication 是释CustomUserService中循环添加到 GrantedAuthority 对象中的权限信息集合. //object 包含客户端发起的请求的requset信息,可转换为 HttpServletRequest request = ((FilterInvocation) object).getHttpRequest(); //configAttributes 为MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法返回的结果,此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法,用来判定用户是否有此权限。如果不在权限表中则放行。 @Override public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException { if(null== configAttributes || configAttributes.size() <=0) { return; } ConfigAttribute c; String needRole; for(Iterator<ConfigAttribute> iter = configAttributes.iterator(); iter.hasNext(); ) { c = iter.next(); needRole = c.getAttribute(); for(GrantedAuthority ga : authentication.getAuthorities()) {//authentication 为在注释1 中循环添加到 GrantedAuthority 对象中的权限信息集合 if(needRole.trim().equals(ga.getAuthority())) { return; } } log.info("【权限管理决断器】需要role:" + needRole); } throw new AccessDeniedException("Access is denied"); } @Override public boolean supports(ConfigAttribute attribute) { return true; } @Override public boolean supports(Class<?> clazz) { return true; } }
5.3、权限配置资源管理器
权限配置资源管理器实现了FilterInvocationSecurityMetadataSource,在启动时就去加载了所有的权限列表,权限配置资源管理器为决断器实时提供支持,判断用户访问的资源是否在受保护的范围之内。
package com.example.demo.utils.security; import com.example.demo.dao.SysPermissionDao; import com.example.demo.pojo.SysPermission; 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.matcher.AntPathRequestMatcher; import org.springframework.stereotype.Service; import javax.servlet.http.HttpServletRequest; import java.util.*; /** * 权限配置资源管理器 * * @Author: 我爱大金子 * @Description: 权限配置资源管理器 * @Date: Create in 17:17 2017/7/5 */ @Service public class CustomFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { @Autowired private SysPermissionDao sysPermissionDao; private HashMap<String, Collection<ConfigAttribute>> map =null; /** * 加载权限表中所有权限 */ public void loadResourceDefine(){ map = new HashMap<>(); Collection<ConfigAttribute> array; ConfigAttribute cfg; List<SysPermission> permissions = sysPermissionDao.findAll(); for(SysPermission permission : permissions) { array = new ArrayList<>(); cfg = new SecurityConfig(permission.getName()); //此处只添加了用户的名字,其实还可以添加更多权限的信息,例如请求方法到ConfigAttribute的集合中去。此处添加的信息将会作为MyAccessDecisionManager类的decide的第三个参数。 array.add(cfg); //用权限的getUrl() 作为map的key,用ConfigAttribute的集合作为 value, map.put(permission.getUrl(), array); } } //此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法,用来判定用户是否有此权限。如果不在权限表中则放行。 @Override public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { if(map ==null) loadResourceDefine(); //object 中包含用户请求的request 信息 HttpServletRequest request = ((FilterInvocation) object).getHttpRequest(); AntPathRequestMatcher matcher; String resUrl; for(Iterator<String> iter = map.keySet().iterator(); iter.hasNext(); ) { resUrl = iter.next(); matcher = new AntPathRequestMatcher(resUrl); if(matcher.matches(request)) { return map.get(resUrl); } } return null; } @Override public Collection<ConfigAttribute> getAllConfigAttributes() { return null; } @Override public boolean supports(Class<?> clazz) { return true; } }
六、根据权限设置连接
对于权限管理,我们可能希望,在一个用户访问的界面中,不是等到用户点击了超链接之后,才来判断用户有没有这个权限,而是按照用户拥有的权限来显示超链接。这样的设计对于用户体验来说,会更友好。
6.1、方法1:使用sec标签(thymeleaf)
在html标签中引入的Spring Security的标签:
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4"
sec:authentication="name":取当前登录用户的用户名
<title sec:authentication="name"></title>
sec:authorize="hasRole(‘ROLE_ADMIN‘):表示当前用户是否拥有角色ROLE_ADMIN
<li sec:authorize="hasRole(‘ROLE_ADMIN‘)"><a th:href="@{/admin}"> admin </a></li>
本文出自 “我爱大金子” 博客,请务必保留此出处http://1754966750.blog.51cto.com/7455444/1945094
springBoot(23):spring-security-
原文地址:http://1754966750.blog.51cto.com/7455444/1945094