Spring Boot安全设计的配置
Posted 释然的蛋
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring Boot安全设计的配置相关的知识,希望对你有一定的参考价值。
Web应用的安全管理,包括两个方面:一是用户身份认证,即用户登录的设计;另一方面是用户的授权,即一个用户在一个应用系统中能够执行哪些操作的权限管理。我这里使用spring-cloud-security进行安全管理。
首先是依赖配置
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-parent</artifactId>
<version>Brixton.M5</version>
<relativePath/>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
安全策略配置
@Configuration @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) @EnableConfigurationProperties(SecuritySettings.class) public class SecurityConfiguration extends WebSecurityConfigurerAdapter { protected Log log = LogFactory.getLog(getClass()); @Autowired private AuthenticationManager authenticationManager; @Autowired private SecuritySettings settings; @Autowired private CustomUserDetailsService customUserDetailsService; @Autowired @Qualifier("dataSource") private DataSource dataSource; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(customUserDetailsService).passwordEncoder(passwordEncoder()); //remember me auth.eraseCredentials(false); } @Override protected void configure(HttpSecurity http) throws Exception {//setting是自定义的配置参数 http.formLogin().loginPage("/login").permitAll().successHandler(loginSuccessHandler()) //设定一个自定义的的登陆页面URL .and().authorizeRequests() .antMatchers("/images/**", "/checkcode", "/scripts/**", "/styles/**").permitAll() //完全允许访问的一些URL配置 .antMatchers(settings.getPermitall().split(",")).permitAll() .anyRequest().authenticated() .and().csrf().requireCsrfProtectionMatcher(csrfSecurityRequestMatcher()) //跨站请求伪造,这是一个防止跨站请求伪造攻击的策略配置 .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER) .and().logout().logoutSuccessUrl(settings.getLogoutsuccssurl()) //设定登出成功的链接 .and().exceptionHandling().accessDeniedPage(settings.getDeniedpage()) //配置拒绝访问的提示链接 .and().rememberMe().tokenValiditySeconds(86400).tokenRepository(tokenRepository()); //用来记住用户的登录状态,用户没执行推出下次打开页面不用登陆,时效自己设置 } @Bean public BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public JdbcTokenRepositoryImpl tokenRepository(){ JdbcTokenRepositoryImpl jtr = new JdbcTokenRepositoryImpl(); jtr.setDataSource(dataSource); return jtr; } @Bean public LoginSuccessHandler loginSuccessHandler(){//设置登陆成功处理器 return new LoginSuccessHandler(); } @Bean public CustomFilterSecurityInterceptor customFilter() throws Exception{ CustomFilterSecurityInterceptor customFilter = new CustomFilterSecurityInterceptor(); customFilter.setSecurityMetadataSource(securityMetadataSource()); customFilter.setAccessDecisionManager(accessDecisionManager()); customFilter.setAuthenticationManager(authenticationManager); return customFilter; } @Bean public CustomAccessDecisionManager accessDecisionManager() {// return new CustomAccessDecisionManager(); } @Bean public CustomSecurityMetadataSource securityMetadataSource() { return new CustomSecurityMetadataSource(settings.getUrlroles()); } private CsrfSecurityRequestMatcher csrfSecurityRequestMatcher(){ //加入需要排除阻止CSRF攻击的链表链接,链接地址中包含/rest字符串的,对其忽略CSRF保护策略 CsrfSecurityRequestMatcher csrfSecurityRequestMatcher = new CsrfSecurityRequestMatcher(); List<String> list = new ArrayList<String>(); list.add("/rest/"); csrfSecurityRequestMatcher.setExecludeUrls(list); return csrfSecurityRequestMatcher; } }
自定义的securityconfig配置,放在application.yml中
securityconfig: logoutsuccssurl: / permitall: /rest/**,/bbs** deniedpage: /deny urlroles: /**/new/** = admin; /**/edit/** = admin,editor; /**/delete/** = admin
权限管理规则
@ConfigurationProperties(prefix="securityconfig") public class SecuritySettings { private String logoutsuccssurl = "/logout"; private String permitall = "/api"; private String deniedpage = "/deny"; private String urlroles; public String getLogoutsuccssurl() {//定义推出成功的链接 return logoutsuccssurl; } public void setLogoutsuccssurl(String logoutsuccssurl) { this.logoutsuccssurl = logoutsuccssurl; } public String getPermitall() {//定义允许访问的URL列表 return permitall; } public void setPermitall(String permitall) { this.permitall = permitall; } public String getDeniedpage() { return deniedpage; } public void setDeniedpage(String deniedpage) {//定义拒绝访问的信息提示链接 this.deniedpage = deniedpage; } public String getUrlroles() { return urlroles; } public void setUrlroles(String urlroles) {//链接地质与角色权限的配置列表 this.urlroles = urlroles; } }
防攻击策略
public class CsrfSecurityRequestMatcher implements RequestMatcher { protected Log log = LogFactory.getLog(getClass()); private Pattern allowedMethods = Pattern .compile("^(GET|HEAD|TRACE|OPTIONS)$"); /** * 需要排除的url列表 */ private List<String> execludeUrls; @Override public boolean matches(HttpServletRequest request) { if (execludeUrls != null && execludeUrls.size() > 0) { String servletPath = request.getServletPath(); for (String url : execludeUrls) { if (servletPath.contains(url)) { log.info("++++"+servletPath); return false; } } } return !allowedMethods.matcher(request.getMethod()).matches(); } public List<String> getExecludeUrls() { return execludeUrls; } public void setExecludeUrls(List<String> execludeUrls) { this.execludeUrls = execludeUrls; } }
public class CustomAccessDecisionManager implements AccessDecisionManager { private static final Logger logger = Logger.getLogger(CustomAccessDecisionManager.class); @Override public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException { if (configAttributes == null) { return; } //config urlroles Iterator<ConfigAttribute> iterator = configAttributes.iterator(); while (iterator.hasNext()) { ConfigAttribute configAttribute = iterator.next(); //need role String needRole = configAttribute.getAttribute(); //user roles for (GrantedAuthority ga : authentication.getAuthorities()) { if (needRole.equals(ga.getAuthority())) { return; } } logger.info("need role is " + needRole); } throw new AccessDeniedException("Cannot Access!"); } @Override public boolean supports(ConfigAttribute configAttribute) { return true; } @Override public boolean supports(Class<?> clazz) { return true; } }
public class CustomFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter { private static final Logger logger = Logger.getLogger(CustomFilterSecurityInterceptor.class); private FilterInvocationSecurityMetadataSource securityMetadataSource; @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { FilterInvocation fi = new FilterInvocation(request, response, chain); logger.debug("===="+fi.getRequestUrl()); invoke(fi); } public void invoke(FilterInvocation fi) throws IOException, ServletException { InterceptorStatusToken token = super.beforeInvocation(fi); try { fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } catch (Exception e) { logger.error(e.getMessage()); } finally { super.afterInvocation(token, null); } } public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() { return this.securityMetadataSource; } @Override public Class<? extends Object> getSecureObjectClass() { return FilterInvocation.class; } @Override public SecurityMetadataSource obtainSecurityMetadataSource() { return this.securityMetadataSource; } public void setSecurityMetadataSource( FilterInvocationSecurityMetadataSource smSource) { this.securityMetadataSource = smSource; } public void destroy() { // TODO Auto-generated method stub } public void init(FilterConfig arg0) throws ServletException { // TODO Auto-generated method stub } }
public class CustomSecurityMetadataSource implements FilterInvocationSecurityMetadataSource{ private static final Logger logger = Logger.getLogger(CustomSecurityMetadataSource .class); private Map<String, Collection<ConfigAttribute>> resourceMap = null; private PathMatcher pathMatcher = new AntPathMatcher(); private String urlroles; @Override public Collection<ConfigAttribute> getAllConfigAttributes() { return null; } public CustomSecurityMetadataSource (String urlroles) { super(); this.urlroles = urlroles; resourceMap = loadResourceMatchAuthority(); } private Map<String, Collection<ConfigAttribute>> loadResourceMatchAuthority() { Map<String, Collection<ConfigAttribute>> map = new HashMap<String, Collection<ConfigAttribute>>(); if(urlroles != null && !urlroles.isEmpty()){ String[] resouces = urlroles.split(";"); for(String resource : resouces){ String[] urls = resource.split("="); String[] roles = urls[1].split(","); Collection<ConfigAttribute> list = new ArrayList<ConfigAttribute>(); for(String role : roles){ ConfigAttribute config = new SecurityConfig(role.trim()); list.add(config); } //key:url, value:roles map.put(urls[0].trim(), list); } }else{ logger.error("‘securityconfig.urlroles‘ must be set"); } logger.info("Loaded UrlRoles Resources."); return map; } @Override public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { String url = ((FilterInvocation) object).getRequestUrl(); logger.debug("request url is " + url); if(resourceMap == null) resourceMap = loadResourceMatchAuthority(); Iterator<String> ite = resourceMap.keySet().iterator(); while (ite.hasNext()) { String resURL = ite.next(); if (pathMatcher.match(resURL,url)) { return resourceMap.get(resURL); } } return resourceMap.get(url); } public boolean supports(Class<?> clazz) { return true; } }
以上是关于Spring Boot安全设计的配置的主要内容,如果未能解决你的问题,请参考以下文章
Spring boot:thymeleaf 没有正确渲染片段
Spring boot +Spring Security + Thymeleaf 认证失败返回错误信息