实现自定义的登录认证。
登录成功,生成token并将token 交由redis管理。
登录后对用户访问的接口进行接口级别权限认证。
springSecurity提供的注解权限校验适合的场景是系统中仅有固定的几个角色,且角色的凭证不可修改(如果修改需要改动代码)。
@PreAuthorize("hasAuthority('ROLE_TELLER')")?
public?Account?post(Account?account,?double?amount);?
注:ROLE_TELLER是写死的。
后端系统的访问请求有以下几种类型:
登录、登出(可自定义url)
匿名用户可访问的接口(静态资源,demo示例等)
其他接口(在登录的前提下,继续判断访问者是否有权限访问)
<!--springSecurity安全框架-->
<dependency>
????<groupId>org.springframework.boot</groupId>
????<artifactId>spring-boot-starter-security</artifactId>
????<version>2.3.4.RELEASE</version>
</dependency>
<!--?默认通过SESSIONId改为通过请求头与redis配合验证session?-->
<dependency>
????<groupId>org.springframework.session</groupId>
????<artifactId>spring-session-data-redis</artifactId>
????<version>2.3.1.RELEASE</version>
</dependency>
<!--redis支持-->
<dependency>
????<groupId>org.springframework.boot</groupId>
????<artifactId>spring-boot-starter-data-redis</artifactId>
????<version>2.3.4.RELEASE</version>
</dependency>
注:springBoot版本也是2.3.4.RELEASE,如果有版本对应问题,自行解决。有用到swagger,为了便于测试。
WebSecurityConfig作为springSecurity的主配置文件。
@Configuration
@EnableWebSecurity
public?class?WebSecurityConfig?extends?WebSecurityConfigurerAdapter?{
????
????/**
?????*?Swagger等静态资源不进行拦截
?????*/
????@Override
????public?void?configure(WebSecurity?web)?{
????????web.ignoring().antMatchers(
????????????????"/*.html",
????????????????"/favicon.ico",
????????????????"/**/*.html",
????????????????"/**/*.css",
????????????????"/**/*.js",
????????????????"/error",
????????????????"/webjars/**",
????????????????"/resources/**",
????????????????"/swagger-ui.html",
????????????????"/swagger-resources/**",
????????????????"/v2/api-docs");
????}
????@Override
????protected?void?configure(HttpSecurity?http)?throws?Exception?{
????????http.authorizeRequests()
????????????????//配置一些不需要登录就可以访问的接口
????????????????.antMatchers("/demo/**",?"/about/**").permitAll()
????????????????//任何尚未匹配的URL只需要用户进行身份验证
????????????????.anyRequest().authenticated()
????????????????.and()
????????????????.formLogin()//允许用户进行基于表单的认证
????????????????.loginPage("/mylogin");
????}
}
注:证明可以访问静态资源不会被拦截
我们需要自定义:
登录过滤器:负责过滤登录请求,再交由自定义的登录认证管理器处理。
登录成功处理类:顾名思义,登录成功后的一些处理(设置返回信息提示“登录成功!”,返回数据类型为json)。
登录失败处理类:类似登录成功处理类。Ps:登录成功处理类和失败处理类有默认的实现可以不自定义。但是建议自定义,因为返回的信息为英文,一般情况不符合要求。
登录认证管理器:根据过滤器传过来的登录参数,进行登录认证,认证后授权。
需要实现 AuthenticationSuccessHandler
@Component
public?class?CustomAuthenticationSuccessHandler?implements?AuthenticationSuccessHandler?{
????private?static?final?Logger?LOGGER?=?LoggerFactory.getLogger(CustomAuthenticationSuccessHandler.class);
????@Override
????public?void?onAuthenticationSuccess(HttpServletRequest?request,?HttpServletResponse?response,?Authentication?authentication)?throws?IOException?{
????????response.setContentType(MediaType.APPLICATION_JSON_VALUE);
????????response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
????????//登录成功返回的认证体,具体格式在后面的登录认证管理器中
????????String?responseJson?=?JackJsonUtil.object2String(ResponseFactory.success(authentication));
????????if?(LOGGER.isDebugEnabled())?{
????????????LOGGER.debug("登录成功!");
????????}
????????response.getWriter().write(responseJson);
????}
}
实现 AuthenticationFailureHandler
@Component
public?class?CustomAuthenticationFailureHandler?implements?AuthenticationFailureHandler?{
????private?static?final?Logger?LOGGER?=?LoggerFactory.getLogger(CustomAuthenticationFailureHandler.class);
????@Override
????public?void?onAuthenticationFailure(HttpServletRequest?request,?HttpServletResponse?response,?AuthenticationException?e)?throws?IOException?{
????????String?errorMsg;
????????if?(StringUtils.isNotBlank(e.getMessage()))?{
????????????errorMsg?=?e.getMessage();
????????}?else?{
????????????errorMsg?=?CodeMsgEnum.LOG_IN_FAIL.getMsg();
????????}
????????response.setContentType(MediaType.APPLICATION_JSON_VALUE);
????????response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
????????String?responseJson?=?JackJsonUtil.object2String(ResponseFactory.fail(CodeMsgEnum.LOG_IN_FAIL,errorMsg));
????????if?(LOGGER.isDebugEnabled())?{
????????????LOGGER.debug("认证失败!");
????????}
????????response.getWriter().write(responseJson);
????}
}
新建登录认证管理器
实现 AuthenticationProvider ,负责具体的身份认证(一般数据库认证,在登录过滤器过滤掉请求后传入)
@Component
public?class?UserVerifyAuthenticationProvider?implements?AuthenticationProvider?{
????private?PasswordEncoder?passwordEncoder;
????@Autowired
????private?UserService?userService;
????@Override
????public?Authentication?authenticate(Authentication?authentication)?throws?AuthenticationException?{
????????String?userName?=?(String)?authentication.getPrincipal();?//?Principal?主体,一般指用户名
????????String?passWord?=?(String)?authentication.getCredentials();?//Credentials?网络凭证,一般指密码
????????//通过账号去数据库查询用户以及用户拥有的角色信息
????????UserRoleVo?userRoleVo?=?userService.findUserRoleByAccount(userName);
????????//数据库密码
????????String?encodedPassword?=?userRoleVo.getPassWord();
????????//credentials凭证即为前端传入密码,因为前端一般用Base64加密过所以需要解密。
????????String?credPassword?=?new?String(Base64Utils.decodeFromString(passWord),?StandardCharsets.UTF_8);
????????//?验证密码:前端明文,数据库密文
????????passwordEncoder?=?new?MD5Util();
????????if?(!passwordEncoder.matches(credPassword,?encodedPassword))?{
????????????throw?new?AuthenticationServiceException("账号或密码错误!");
????????}
????????//ps:GrantedAuthority对认证主题的应用层面的授权,含当前用户的权限信息,通常使用角色表示
????????List<GrantedAuthority>?roles?=?new?LinkedList<>();
????????List<Role>?roleList?=?userRoleVo.getRoleList();
????????roleList.forEach(role?->?{
????????????SimpleGrantedAuthority?roleId?=?new?SimpleGrantedAuthority(role.getRoleId().toString());
????????????roles.add(roleId);
????????});
????????UsernamePasswordAuthenticationToken?token?=?new?UsernamePasswordAuthenticationToken(userName,?passWord,?roles);
????????token.setDetails(userRoleVo);//这里可以放用户的详细信息
????????return?token;
????}
????@Override
????public?boolean?supports(Class<?>?authentication)?{
????????return?false;
????}
}
新建登录过滤器
LoginFilter.java继承UsernamePasswordAuthenticationFilter
,负责过滤登录请求并交由登录认证管理器进行具体的认证。
public?class?LoginFilter?extends?UsernamePasswordAuthenticationFilter?{
????private?UserVerifyAuthenticationProvider?authenticationManager;
????/**
?????*?@param?authenticationManager?认证管理器
?????*?@param?successHandler?认证成功处理类
?????*?@param?failureHandler?认证失败处理类
?????*/
????public?LoginFilter(UserVerifyAuthenticationProvider?authenticationManager,
???????????????????????CustomAuthenticationSuccessHandler?successHandler,
???????????????????????CustomAuthenticationFailureHandler?failureHandler)?{
????????//设置认证管理器(对登录请求进行认证和授权)
????????this.authenticationManager?=?authenticationManager;
????????//设置认证成功后的处理类
????????this.setAuthenticationSuccessHandler(successHandler);
????????//设置认证失败后的处理类
????????this.setAuthenticationFailureHandler(failureHandler);
????????//可以自定义登录请求的url
????????super.setFilterProcessesUrl("/myLogin");
????}
????@Override
????public?Authentication?attemptAuthentication(HttpServletRequest?request,?HttpServletResponse?response)?throws?AuthenticationException?{
????????try?{
????????????//转换请求入参
????????????UserDTO?loginUser?=?new?ObjectMapper().readValue(request.getInputStream(),?UserDTO.class);
????????????//入参传入认证管理器进行认证
????????????return?authenticationManager.authenticate(
????????????????????new?UsernamePasswordAuthenticationToken(loginUser.getUserName(),?loginUser.getPassWord())
????????????);
????????}?catch?(IOException?e)?{
????????????e.printStackTrace();
????????????return?null;
????????}
????}
}
最后配置到WebSecurityConfig中:
@Configuration
@EnableWebSecurity
public?class?WebSecurityConfig?extends?WebSecurityConfigurerAdapter?{
????@Autowired
????private?UserVerifyAuthenticationProvider?authenticationManager;//认证用户类
????@Autowired
????private?CustomAuthenticationSuccessHandler?successHandler;//登录认证成功处理类
????@Autowired
????private?CustomAuthenticationFailureHandler?failureHandler;//登录认证失败处理类
????/**
?????*?Swagger等静态资源不进行拦截
?????*/
????@Override
????public?void?configure(WebSecurity?web)?{
????????web.ignoring().antMatchers(
????????????????"/*.html",
????????????????"/favicon.ico",
????????????????"/**/*.html",
????????????????"/**/*.css",
????????????????"/**/*.js",
????????????????"/error",
????????????????"/webjars/**",
????????????????"/resources/**",
????????????????"/swagger-ui.html",
????????????????"/swagger-resources/**",
????????????????"/v2/api-docs");
????}
????@Override
????protected?void?configure(HttpSecurity?http)?throws?Exception?{
????????http.authorizeRequests()
????????????????//配置一些不需要登录就可以访问的接口
????????????????.antMatchers("/demo/**",?"/about/**").permitAll()
????????????????//任何尚未匹配的URL只需要用户进行身份验证
????????????????.anyRequest().authenticated()
????????????????.and()
????????????????//配置登录过滤器
????????????????.addFilter(new?LoginFilter(authenticationManager,?successHandler,?failureHandler))
????????????????.csrf().disable();
????}
}
访问登录请求:
成功进入LoginFilter
session:
????store-type:?redis
????redis:
??????namespace:?spring:session:admin
????#?session?无操作失效时间?30?分钟
????timeout:?1800
设置token放入返回的header中需要在WebSecurityConfig中加入
/**
?*?配置?HttpSessionIdResolver?Bean
?*?登录之后将会在?Response?Header?x-auth-token?中?返回当前?sessionToken
?*?将token存储在前端?每次调用的时候?Request?Header?x-auth-token?带上?sessionToken
?*/
@Bean
public?HttpSessionIdResolver?httpSessionIdResolver()?{
????return?HeaderHttpSessionIdResolver.xAuthToken();
}
关于安全头信息可以参考:
https://docs.spring.io/spring-security/site/docs/5.2.1.BUILD-SNAPSHOT/reference/htmlsingle/#ns-headers
设置安全请求头需要设置WebSecurityConfig中加入
protected?void?configure(HttpSecurity?http)?throws?Exception?{
????????http.authorizeRequests()
????????????????//配置一些不需要登录就可以访问的接口
????????????????.antMatchers("/demo/**",?"/about/**").permitAll()
????????????????//任何尚未匹配的URL只需要用户进行身份验证
????????????????.anyRequest().authenticated()
????????????????.and()
????????????????//配置登录过滤器
????????????????.addFilter(new?LoginFilter(authenticationManager,?successHandler,?failureHandler))
????????????????.csrf().disable();
????????//配置头部
????????http.headers()
????????????????.contentTypeOptions()
????????????????.and()
????????????????.xssProtection()
????????????????.and()
????????????????//禁用缓存
????????????????.cacheControl()
????????????????.and()
????????????????.httpStrictTransportSecurity()
????????????????.and()
????????????????//禁用页面镶嵌frame劫持安全协议??//?防止iframe?造成跨域
????????????????.frameOptions().disable();
????}
进行登录测试,验证结果:
注:响应中有token
查看redis。成功保存进了redis
如下图,详细请看链接。
注:不使用这种方式,原因在于,需要自己判断是否匿名用户。
方法二:
Spring Security使用FilterSecurityInterceptor
过滤器来进行URL权限校验,实际使用流程大致如下:
正常情况的接口权限判断:
1、定义一个MyFilterInvocationSecurityMetadataSource
实现FilterInvocationSecurityMetadataSource
的类,重写getAttributes方法。
方法的作用是:返回哪些角色可以访问当前url,这个肯定是从数据库中获取。要注意的是对于PathVariable传参的url,数据库中存的是这样的:/getUserByName/{name}
。但实际访问的url中name是具体的值。类似的/user/getUserById?
也要可以匹配?/user/getUserById?1
。
package?com.aliyu.security.provider;/**
?*?@author:?aliyu
?*?@create:?2021-02-05?14:53
?*?@description:
?*/
import?com.aliyu.service.role.RoleService;
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.Component;
import?javax.servlet.http.HttpServletRequest;
import?java.util.Collection;
import?java.util.List;
import?java.util.Map;
/**
?*@author:?aliyu
?*@create:
?*@description:?第一步:数据库查询所有权限出来:
?*?之所以要所有权限,因为数据库url和实际请求url并不能直接匹配需要。比方:/user/getUserById 匹配?/user/getUserById?1
?*?第二步:通过httpUrl匹配器找出允许访问当前请求的角色列表(哪些角色可以访问此请求)
?*/
@Component
public?class?MyFilterInvocationSecurityMetadataSource?implements?FilterInvocationSecurityMetadataSource?{
????@Autowired
????private?RoleService?roleService;
????/**
?????*?返回当前URL允许访问的角色列表
?????*?@param?object
?????*?@return
?????*?@throws?IllegalArgumentException
?????*/
????@Override
????public?Collection<ConfigAttribute>?getAttributes(Object?object)?throws?IllegalArgumentException?{
????????//入参转为HttpServletRequest
????????FilterInvocation?fi?=?(FilterInvocation)?object;
????????HttpServletRequest?request?=?fi.getRequest();
????????//从数据库中查询系统所有的权限,格式为<"权限url","能访问url的逗号分隔的roleid">
????????List<Map<String,?String>>?allUrlRoleMap?=?roleService.getAllUrlRoleMap();
????????for?(Map<String,?String>?urlRoleMap?:?allUrlRoleMap)?{
????????????String?url?=?urlRoleMap.get("url");
????????????String?roles?=?urlRoleMap.get("roles");
????????????//new AntPathRequestMatcher创建httpUrl匹配器:里面url匹配规则已经给我们弄好了,
????????????//?能够支持校验PathVariable传参的url(例如:/getUserByName/{name})
????????????//?也能支持?/user/getUserById?匹配?/user/getUserById?1
????????????AntPathRequestMatcher?matcher?=?new?AntPathRequestMatcher(url);
????????????if?(matcher.matches(request)){?//当前请求与httpUrl匹配器进行匹配
????????????????return?SecurityConfig.createList(roles.split(","));
????????????}
????????}
????????return?null;
????}
????@Override
????public?Collection<ConfigAttribute>?getAllConfigAttributes()?{
????????return?null;
????}
????@Override
????public?boolean?supports(Class<?>?clazz)?{
????????return?FilterInvocation.class.isAssignableFrom(clazz);
????}
}
注:别人是初始化的时候加载所有权限,一次就好了。我的是每次请求都会去重新加载系统所有权限,好处就是不用担心权限修改的问题。
定义一个MyAccessDecisionManager
:通过实现AccessDecisionManager
接口自定义一个决策管理器,判断是否有访问权限。上一步MyFilterInvocationSecurityMetadataSource
中返回的当前请求可以的访问角色列表会传到这里的decide方法里面(如果没有角色的话,不会进入decide方法。
正常情况你访问的url必然和某个角色关联,如果没有关联就不应该可以访问)。decide方法传了当前登录用户拥有的角色,通过判断用户拥有的角色中是否有一个角色和当前url可以访问的角色匹配。如果匹配,权限校验通过。
package?com.aliyu.security.provider;/**
?*?@description:
?*/
import?org.apache.commons.lang3.StringUtils;
import?org.springframework.security.access.AccessDecisionManager;
import?org.springframework.security.access.AccessDeniedException;
import?org.springframework.security.access.ConfigAttribute;
import?org.springframework.security.authentication.AnonymousAuthenticationToken;
import?org.springframework.security.authentication.InsufficientAuthenticationException;
import?org.springframework.security.core.Authentication;
import?org.springframework.security.core.GrantedAuthority;
import?org.springframework.security.web.FilterInvocation;
import?org.springframework.stereotype.Component;
import?java.util.Collection;
import?java.util.Iterator;
/**
?*@author:?aliyu
?*@create:
?*@description:?接口权限判断(根据MyFilterInvocationSecurityMetadataSource获取到的请求需要的角色
?*?和当前登录人的角色进行比较)
?*/
@Component
public?class?MyAccessDecisionManager?implements?AccessDecisionManager?{
????@Override
????public?void?decide(Authentication?authentication,?Object?object,?Collection<ConfigAttribute>?configAttributes)?throws?AccessDeniedException,?InsufficientAuthenticationException?{
????????//循环请求需要的角色,只要当前用户拥有的角色中包含请求需要的角色中的一个,就算通过。
????????Iterator<ConfigAttribute>?iterator?=?configAttributes.iterator();
????????while(iterator.hasNext()){
????????????ConfigAttribute?configAttribute?=?iterator.next();
????????????String?needCode?=?configAttribute.getAttribute();
????????????//获取到了登录用户的所有角色
????????????Collection<??extends?GrantedAuthority>?authorities?=?authentication.getAuthorities();
????????????for?(GrantedAuthority?authority?:?authorities)?{
????????????????if?(StringUtils.equals(authority.getAuthority(),?needCode))?{
????????????????????return;
????????????????}
????????????}
????????}
????????throw?new?AccessDeniedException("当前访问没有权限");
????}
????@Override
????public?boolean?supports(ConfigAttribute?attribute)?{
????????return?false;
????}
????@Override
????public?boolean?supports(Class<?>?clazz)?{
????????return?FilterInvocation.class.isAssignableFrom(clazz);
????}
}
1、定义一个CustomAuthenticationEntryPoint
实现AuthenticationEntryPoint
处理匿名用户访问无权限资源(可以理解为未登录的用户访问,确实有些接口是可以不登录也能访问的,比较少,我们在WebSecurityConfig
已经配置过了。如果多的话,需要另外考虑从数据库中获取,并且权限需要加一个标志它为匿名用户可访问)。
package?com.aliyu.security.handler;
import?com.aliyu.common.util.JackJsonUtil;
import?com.aliyu.entity.common.vo.ResponseFactory;
import?com.aliyu.security.constant.MessageConstant;
import?org.slf4j.Logger;
import?org.slf4j.LoggerFactory;
import?org.springframework.http.MediaType;
import?org.springframework.security.core.AuthenticationException;
import?org.springframework.security.web.AuthenticationEntryPoint;
import?javax.servlet.http.HttpServletRequest;
import?javax.servlet.http.HttpServletResponse;
import?java.io.IOException;
import?java.nio.charset.StandardCharsets;
import?static?com.aliyu.entity.common.exception.CodeMsgEnum.MOVED_PERMANENTLY;
/**
?*?未登录重定向处理器
?*?<p>
?*?未登录状态下访问需要登录的接口
?*
?*?@author
?*/
public?class?CustomAuthenticationEntryPoint?implements?AuthenticationEntryPoint?{
????private?static?final?Logger?LOGGER?=?LoggerFactory.getLogger(CustomAuthenticationEntryPoint.class);
????@Override
????public?void?commence(HttpServletRequest?request,?HttpServletResponse?response,?AuthenticationException?e)?throws?IOException?{
????????response.setContentType(MediaType.APPLICATION_JSON_VALUE);
????????response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
????????//原来不需要登录的接口,现在需要登录了,所以叫永久移动
????????String?message?=?JackJsonUtil.object2String(
????????????????ResponseFactory.fail(MOVED_PERMANENTLY,?MessageConstant.NOT_LOGGED_IN)
????????);
????????if?(LOGGER.isDebugEnabled())?{
????????????LOGGER.debug("未登录重定向!");
????????}
????????response.getWriter().write(message);
????}
}
2、定义一个CustomAccessDeniedHandler
?实现AccessDeniedHandler
处理登陆认证过的用户访问无权限资源。
package?com.aliyu.security.handler;
import?com.aliyu.common.util.JackJsonUtil;
import?com.aliyu.entity.common.exception.CodeMsgEnum;
import?com.aliyu.entity.common.vo.ResponseFactory;
import?com.aliyu.security.constant.MessageConstant;
import?org.slf4j.Logger;
import?org.slf4j.LoggerFactory;
import?org.springframework.http.MediaType;
import?org.springframework.security.access.AccessDeniedException;
import?org.springframework.security.web.access.AccessDeniedHandler;
import?javax.servlet.http.HttpServletRequest;
import?javax.servlet.http.HttpServletResponse;
import?java.io.IOException;
import?java.nio.charset.StandardCharsets;
/**
?*?拒绝访问处理器(登录状态下,访问没有权限的方法时会进入此处理器)
?*
?*?@author
?*/
public?class?CustomAccessDeniedHandler?implements?AccessDeniedHandler?{
????private?static?final?Logger?LOGGER?=?LoggerFactory.getLogger(CustomAccessDeniedHandler.class);
????@Override
????public?void?handle(HttpServletRequest?request,?HttpServletResponse?response,?AccessDeniedException?e)?throws?IOException?{
????????response.setContentType(MediaType.APPLICATION_JSON_VALUE);
????????response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
????????String?message?=?JackJsonUtil.object2String(
????????????????ResponseFactory.fail(CodeMsgEnum.UNAUTHORIZED,?MessageConstant.NO_ACCESS)
????????);
????????if(LOGGER.isDebugEnabled()){
????????????LOGGER.debug("没有权限访问!");
????????}
????????response.getWriter().write(message);
????}
}
配置到WebSecurityConfig上面去
@Autowired
private?MyFilterInvocationSecurityMetadataSource?securityMetadataSource;//返回当前URL允许访问的角色列表
@Autowired
private?MyAccessDecisionManager?accessDecisionManager;//除登录登出外所有接口的权限校验
.withObjectPostProcessor(new?ObjectPostProcessor<FilterSecurityInterceptor>()?{
?????@Override
?????public?<O?extends?FilterSecurityInterceptor>?O?postProcess(O?object)?{
?????????object.setAccessDecisionManager(accessDecisionManager);
?????????object.setSecurityMetadataSource(securityMetadataSource);
?????????return?object;
?????}
?})
//用来解决匿名用户访问无权限资源时的异常
.exceptionHandling().authenticationEntryPoint(new?CustomAuthenticationEntryPoint())
//用来解决登陆认证过的用户访问无权限资源时的异常
.accessDeniedHandler(new?CustomAccessDeniedHandler())
完整的Java类:
package?com.aliyu.security.config;
import?com.aliyu.filter.LoginFilter;
import?com.aliyu.security.handler.*;
import?com.aliyu.security.provider.MyAccessDecisionManager;
import?com.aliyu.security.provider.MyFilterInvocationSecurityMetadataSource;
import?com.aliyu.security.provider.UserVerifyAuthenticationProvider;
import?org.springframework.beans.factory.annotation.Autowired;
import?org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import?org.springframework.context.annotation.Bean;
import?org.springframework.context.annotation.Configuration;
import?org.springframework.security.config.annotation.ObjectPostProcessor;
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.crypto.bcrypt.BCryptPasswordEncoder;
import?org.springframework.security.crypto.password.PasswordEncoder;
import?org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import?org.springframework.session.web.http.HeaderHttpSessionIdResolver;
import?org.springframework.session.web.http.HttpSessionIdResolver;
@Configuration
@EnableWebSecurity
public?class?WebSecurityConfig?extends?WebSecurityConfigurerAdapter?{
????@Autowired
????private?UserVerifyAuthenticationProvider?authenticationManager;//认证用户类
????@Autowired
????private?CustomAuthenticationSuccessHandler?successHandler;//登录认证成功处理类
????@Autowired
????private?CustomAuthenticationFailureHandler?failureHandler;//登录认证失败处理类
????@Autowired
????private?MyFilterInvocationSecurityMetadataSource?securityMetadataSource;//返回当前URL允许访问的角色列表
????@Autowired
????private?MyAccessDecisionManager?accessDecisionManager;//除登录登出外所有接口的权限校验
????/**
?????*?密码加密
?????*?@return
?????*/
????@Bean
????@ConditionalOnMissingBean(PasswordEncoder.class)
????public?PasswordEncoder?passwordEncoder()?{
????????return?new?BCryptPasswordEncoder();
????}
????/**
?????*?配置?HttpSessionIdResolver?Bean
?????*?登录之后将会在?Response?Header?x-auth-token?中?返回当前?sessionToken
?????*?将token存储在前端?每次调用的时候?Request?Header?x-auth-token?带上?sessionToken
?????*/
????@Bean
????public?HttpSessionIdResolver?httpSessionIdResolver()?{
????????return?HeaderHttpSessionIdResolver.xAuthToken();
????}
????/**
?????*?Swagger等静态资源不进行拦截
?????*/
????@Override
????public?void?configure(WebSecurity?web)?{
????????web.ignoring().antMatchers(
????????????????"/*.html",
????????????????"/favicon.ico",
????????????????"/**/*.html",
????????????????"/**/*.css",
????????????????"/**/*.js",
????????????????"/error",
????????????????"/webjars/**",
????????????????"/resources/**",
????????????????"/swagger-ui.html",
????????????????"/swagger-resources/**",
????????????????"/v2/api-docs");
????}
????@Override
????protected?void?configure(HttpSecurity?http)?throws?Exception?{
????????http.authorizeRequests()
????????????????//配置一些不需要登录就可以访问的接口
????????????????.antMatchers("/demo/**",?"/about/**").permitAll()
????????????????//任何尚未匹配的URL只需要用户进行身份验证
????????????????.anyRequest().authenticated()
????????????????//登录后的接口权限校验
????????????????.withObjectPostProcessor(new?ObjectPostProcessor<FilterSecurityInterceptor>()?{
????????????????????@Override
????????????????????public?<O?extends?FilterSecurityInterceptor>?O?postProcess(O?object)?{
????????????????????????object.setAccessDecisionManager(accessDecisionManager);
????????????????????????object.setSecurityMetadataSource(securityMetadataSource);
????????????????????????return?object;
????????????????????}
????????????????})
????????????????.and()
????????????????//配置登出处理
????????????????.logout().logoutUrl("/logout")
????????????????.logoutSuccessHandler(new?CustomLogoutSuccessHandler())
????????????????.clearAuthentication(true)
????????????????.and()
????????????????//用来解决匿名用户访问无权限资源时的异常
????????????????.exceptionHandling().authenticationEntryPoint(new?CustomAuthenticationEntryPoint())
????????????????//用来解决登陆认证过的用户访问无权限资源时的异常
????????????????.accessDeniedHandler(new?CustomAccessDeniedHandler())
????????????????.and()
????????????????//配置登录过滤器
????????????????.addFilter(new?LoginFilter(authenticationManager,?successHandler,?failureHandler))
????????????????.csrf().disable();
????????//配置头部
????????http.headers()
????????????????.contentTypeOptions()
????????????????.and()
????????????????.xssProtection()
????????????????.and()
????????????????//禁用缓存
????????????????.cacheControl()
????????????????.and()
????????????????.httpStrictTransportSecurity()
????????????????.and()
????????????????//禁用页面镶嵌frame劫持安全协议??//?防止iframe?造成跨域
????????????????.frameOptions().disable();
????}
}
如果这篇文章对您有所帮助,或者有所启发的话,求一键三连:点赞、转发、在看。
关注公众号:woniuxgg,在公众号中回复:笔记??就可以获得蜗牛为你精心准备的java实战语雀笔记,回复面试、开发手册、有超赞的粉丝福利!