1、Spring Security是基于J2EE开发的企业应用软件提供了全面的安全服务,是针对Spring项目的安全框架,也是SpringBoot底层安全模块默认的技术选型,他可以实现强大的Web安全控制,对于安全控制,我们仅需要引入spring-boot-starter-security模块,进行少量的配置,即可实现强大的安全管理
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
<version>2.5.1version>
dependency>
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
}
Spring Security提供了很多过滤器,它们拦截Servlet请求,并将这些请求转交给认证处理过滤器和访问决策过滤器进行处理,并强制安全性认证用户身份和用户权限以达到保护WEB资源的目的,Spring Security安全机制包括两个主要的操作,认证和验证,验证也可以称为权限控制,
这是Spring Security两个主要的方向,认证是为用户建立一个他所声明的主体的过程,这个主体一般是指用户设备或可以在系统中执行行动的其他系统,验证指用户能否在应用中执行某个操作,在到达授权判断之前身份的主体已经由身份认证过程建立了
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//认证:可以基于数据库认证或者内存认证
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("qianqian").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
.and()
.withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
.and()
.withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1");
}
}
SpringSecurity原生的UsernamePasswordAuthenticationFilter仅支持账户和密码的表单参数校验
,对于前端传来的如验证码等其他表单元素,并无法进行验证WebAuthenticationDetails: 该类提供了获取用户登录时携带的额外信息的功能,默认提供了 remoteAddress 与 sessionId 信息。我们还可以添加自己所需要的信息,比如验证码
AuthenticationDetailsSource
public class UserWebAuthenticationDetails extends WebAuthenticationDetails {
private static Logger log = LoggerFactory.getLogger(UserWebAuthenticationDetails.class);
private static final long serialVersionUID = 6975601077710753878L;
private final String code;
/**
* @param request that the authentication request was received from
*/
public UserWebAuthenticationDetails(HttpServletRequest request) {
super(request);
code=request.getParameter("code");
log.info("进入到了获取验证码的过滤器-->WebAuthenticationDetails,获取到的验证码-->"+code);
}
public String getCode() {
return code;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(super.toString()).append("; code: ").append(this.getCode());
return sb.toString();
}
}
@Component("authenticationDetailsSource")
public class UserAuthenticationDetailsSource implements AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> {
private static Logger log = LoggerFactory.getLogger(UserAuthenticationDetailsSource.class);
@Override
public WebAuthenticationDetails buildDetails(HttpServletRequest request) {
log.info("开始替换SpringSecurity原生的WebAuthenticationDetails");
return new UserWebAuthenticationDetails(request);
}
}
替换规则一定要写在.formLogin()这个开启表单验证的后面
/*
获取登录表单额外参数
*/
@Autowired
private AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> authenticationDetailsSource;
.and()
.formLogin()
.loginProcessingUrl("/admin/user/login").permitAll()
.successHandler(getLoginSuccessHandler())
.failureHandler(getLoginFailHandler())
.authenticationDetailsSource(authenticationDetailsSource)
json
,但是呢,这个SpringSecurity呢就离了个大谱,他默认支持的方式x-www-form-urlencoded
方式的!所以我就在这被坑了很长一段时间,找到问题之后就好解决了,前端用qs转换一下数据格式就OK了,转换数据之后如果按正常流程的话,vue会自动识别数据的格式,他还是用自动转换为json数据格式,所以咱们发送请求就使用传统的请求发送就OK了,大功告成 //登录
login(data){
return request.post(`/api/admin/user/login?`+data)
},
//发送登录请求
user.login(qs.stringify(this.loginForm, {arrayFormat: 'indices', allowDots: true}))
//发送的请求格式
http://localhost:8080/api/admin/user/login?username=admin&password=admin&code=wjud
UsernamePasswordAuthenticationFilter
这个过滤器并不足以支持完成我们校验功能,所以我们需要自己去实现一个AuthenticationProvider
。PasswordEncoder
,密码加密规则有很多种,可以使用SpringSecurity原生的,也可以使用自己封装好的工具类,比如MD5等等。UserDetailsService
这个接口!这个接口内部默认需要去实现的方法Public UserDetails loadUserByUsername(String username)
,在这个方法内部我们就可以实现自己的查询逻辑了!但仔细一看这个方法的返回值,介是嘛呀!没见过呀,赶紧点进去看一看,哦,原来是一个接口呀!那就好说了,实现它!讲道理还是要弄清楚的,这个接口里边定义几个很重要的属性!百度一下,哦,原来如此!UserDetails ,该接口是提供用户信息的核心接口
。该接口实现仅仅存储用户的信息。后续会将该接口提供的用户信息封装到认证对象Authentication中去。
@Component
public class LocalAuthenticationProvider implements AuthenticationProvider {
private static Logger log = LoggerFactory.getLogger(LocalAuthenticationProvider.class);
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private DefaultPasswordEncoderUtil defaultPasswordEncoderUtil;
@Autowired
private RedisUtils redisUtils;
@Override
public Authentication authenticate(Authentication authentication) throws BadCredentialsException {
log.info("进入自定义验证规则");
String username = authentication.getName();
String password = (String)authentication.getCredentials();
UserWebAuthenticationDetails details = (UserWebAuthenticationDetails) authentication.getDetails();
// 获取Request, 获取其他参数信息
String code = details.getCode();
//验证码比较
String redisCode = (String)redisUtils.get("code");
if(redisCode.toString().equalsIgnoreCase(code)){
//去找自己实现UserDetails的实现类
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if(defaultPasswordEncoderUtil.matches(password,userDetails.getPassword())){
//用户名密码校验成功
return new UsernamePasswordAuthenticationToken(userDetails, password, userDetails.getAuthorities());
}
log.info("用户名或密码不正确");
throw new BadCredentialsException("用户名或密码不正确!");
}
log.info("验证码不正确");
throw new BadCredentialsException("验证码不正确!");
}
/**
* supports函数用来指明该Provider是否适用于该类型的认证,如果不合适,则寻找另一个Provider进行验证处理
* @param authentication
* @return
*/
@Override
public boolean supports(Class<?> authentication) {
//和SpringSecurity
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
@Component
public class DefaultPasswordEncoderUtil implements PasswordEncoder {
private static Logger log = LoggerFactory.getLogger(DefaultPasswordEncoderUtil.class);
public DefaultPasswordEncoderUtil(){
this(-1);
}
public DefaultPasswordEncoderUtil(int strLength){
}
/**
* 进行MD5密码加密
* @param rawPassword
* @return
*/
@Override
public String encode(CharSequence rawPassword) {
return MD5Utils.encrypt(rawPassword.toString());
}
/**
* 进行密码比较
* @param rawPassword
* @param encodedPassword
* @return
*/
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
log.info("DefaultPasswordEncoderUtil---->前端传入的密码rawPassword==="+rawPassword.toString());
log.info("DefaultPasswordEncoderUtil---->数据库中的密码encodedPassword==="+encodedPassword);
return encodedPassword.equals(MD5Utils.encrypt(rawPassword.toString()));
}
}
@Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {
private static Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class);
@Autowired
private UserService userService;
@Autowired
private MenuService menuService;
/**
* 根据用户名查出用户详细信息
* @param username
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("进入用户验证---->UserDetailsServiceImpl");
//根据用户名查询
ManUser user = userService.queryUser(username);
log.info("查到的用户数据==="+user.toString());
//判断用户信息
if(user == null){
log.info("用户不存在");
throw new UsernameNotFoundException("用户不存在");
}
//查询当前用户所关联的所有菜单
ManUserVO manUserVO = userService.queryUserRelationAllMenu(user);
if(manUserVO==null){
throw new UsernameNotFoundException("用户不存在");
}
List<ManMenu> menuList = manUserVO.getMenuList();
ArrayList<String> menuStringList = new ArrayList<>();
for (ManMenu manMenu : menuList) {
menuStringList.add(manMenu.toString());
}
SecurityUserVO securityUserVO = new SecurityUserVO();
securityUserVO.setCurrentUserInfo(user);
securityUserVO.setPermissionValueList(menuStringList);
log.info("验证数据"+securityUserVO.toString());
//校验账户状态属性是否正常,
return securityUserVO;
}
}
@Data
public class SecurityUserVO implements UserDetails {
//当前登录用户
private transient ManUser currentUserInfo;
//当前权限
private List<String> permissionValueList;
public SecurityUserVO() {
}
public SecurityUserVO(ManUser currentUserInfo) {
if (currentUserInfo!=null) {
this.currentUserInfo = currentUserInfo;
}
}
/**
* 获取角色权限
* @return
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new ArrayList<>();
for (String permissionValue : permissionValueList) {
if (StringUtils.isEmpty(permissionValue)) {
continue;
}
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue);
authorities.add(authority);
}
return authorities;
}
/**
* 获取密码
* @return
*/
@Override
public String getPassword() {
return currentUserInfo.getPassword();
}
/**
* 获取用户名
* @return
*/
@Override
public String getUsername() {
return currentUserInfo.getUsername();
}
/**
* 用户账号是否过期
*/
@Override
public boolean isAccountNonExpired() {
return true;
}
/**
* 用户账号是否被锁定
*/
@Override
public boolean isAccountNonLocked() {
return true;
}
/**
* 用户密码是否过期
*/
@Override
public boolean isCredentialsNonExpired() {
return true;
}
/**
* 用户是否可用
*/
@Override
public boolean isEnabled() {
return true;
}
}
AuthenticationSuccessHandler
,认证成功之后我们需要把用户信息还有后端生成的Token反馈给前端,这里说到Token的保存方式有很多,大家可以自由选择,我这里是把用户信息和token都放到了Redis里边AuthenticationFailureHandler
,失败就直接返回失败原因就好了public class LoginSuccessHandler implements AuthenticationSuccessHandler {
private static Logger log = LoggerFactory.getLogger(LoginSuccessHandler.class);
private JwtUtil jwtUtil;
@Autowired
private RedisUtils redisUtils;
private static String signatureAlgorithmSecret="abcdefghijklmnopqrstuvwxyz123456789";
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
//认证成功,得到认证成功之后的用户信息
log.info("自定义登录处理器认证成功");
log.info("当前的登录时间为:" + new Date().toString());
log.info("当前的登录IP为:" + request.getRemoteAddr());
SecurityUserVO user = (SecurityUserVO) authentication.getPrincipal();
//根据用户名生成token
PayloadDTO payload = jwtUtil.getPayload(user.getCurrentUserInfo());
// 生成token
String payloadJsonString = JSON.toJSONString(payload);
String token;
try {
token = JwtUtil.generateToken(payloadJsonString, signatureAlgorithmSecret);
//把用户名称和用户权限列表存放到redis中
redisUtils.set(user.getCurrentUserInfo().getUsername(),user.getPermissionValueList());
//返回token
Map<String,Object> userInfoAndToken=new HashMap<>();
userInfoAndToken.put("userInfo",user.getCurrentUserInfo());
userInfoAndToken.put("token",token);
ResponseUtil.out(response, Result.result("000","login success",userInfoAndToken));
} catch (JOSEException e) {
e.printStackTrace();
}
}
}
public class LoginErrorHandler implements AuthenticationFailureHandler {
private static Logger log = LoggerFactory.getLogger(LoginErrorHandler.class);
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
log.info("自定义登录处理器认证失败");
ResponseUtil.out(response, Result.result("445","captcha error",null));
}
}
/**
* 自定义校验
*/
@Bean
public AuthenticationProvider authenticationProvider() {
LocalAuthenticationProvider authenticationProvider = new LocalAuthenticationProvider();
return authenticationProvider;
}
// 登录成功处理器
@Bean
public LoginSuccessHandler getLoginSuccessHandler() {
return new LoginSuccessHandler();
}
// 登录失败处理器
@Bean
public LoginErrorHandler getLoginFailHandler() {
return new LoginErrorHandler();
}
’Authorization : Basic token字符串
‘’BasicAuthenticationFilter
这个过滤器。仔细一看发现这个怎么这么熟悉呀,没错 BasicAuthentication不就是token的请求头么! 这个过滤的作用就是处理HTTP请求中的BASIC authorization头部,把认证结果写入SecurityContextHolder。当一个HTTP请求中包含一个名字为Authorization的头部,并且其值格式是Basic xxx时, 该Filter会认为这是一个BASIC authorization头部
,这里边最重要的一个对象要来了,它贯穿整个SpringSecurity中,它就是SecurityContextHolder(Security安全上下文对象)
,作为程序猿,同学们听该听过特别多的什么什么上下文对象了,像什么Spring上下文对象,Application配置类上下文对象。那么这些上下文对象主要的作用是什么呢?上下文即ServletContext,是一个全局的储存信息的空间,服务器启动,其就存在,服务器关闭,其才释放。所有用户共用一个ServletContext。所以,为了节省空间,提高效率,ServletContext中,要放必须的、重要的、所有用户需要共享的线程又是安全的一些信息。
,这么一看下来,这下子同学们心里有答案了吧,我们就是要在这个BasicAuthenticationFilter过滤器的实现类中把权限列表存入上下文对象!在这里我们提到了两个问题,一是怎么拿到当前请求URL,二是怎么判断当前请求URL属不属于当前用户所关联的角色呢?
FilterInvocationSecurityMetadataSource
这个接口可以称为权限资源过滤器,实现动态的权限验证,它的主要责任就是当访问一个url时,返回这个url所需要的访问权限。这里我们就解决了第一个问题AccessDecisionManager
public class TokenAuthFilter extends BasicAuthenticationFilter {
private static Logger log = LoggerFactory.getLogger(TokenAuthFilter.class);
private JwtUtil jwtUtil;
private RedisUtils redisUtils;
public TokenAuthFilter(AuthenticationManager authenticationManager,JwtUtil jwtUtil, RedisUtils redisUtils) {
super(authenticationManager);
this.jwtUtil = jwtUtil;
this.redisUtils = redisUtils;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
log.info("已认证成功,开始授权");
//获取当前认证成功用户权限信息
UsernamePasswordAuthenticationToken authRequest=getAuthentication(request);
if(authRequest != null){
log.info("authRequest==========="+authRequest.toString());
SecurityContextHolder.getContext().setAuthentication(authRequest);
}
chain.doFilter(request,response);
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request){
String token = request.getHeader("Authorization");
if(token != null){
log.info("token========"+token);
//获取用户名
try {
String username = JwtUtil.getUsername();
log.info(username);
//获取该用户所拥有的菜单信息
List<String> permissionValueList = (List<String>) redisUtils.get(username);
if(permissionValueList.isEmpty()){
return null;
}
Collection<GrantedAuthority> authorities=new ArrayList<>();
for (String permissionValue : permissionValueList) {
SimpleGrantedAuthority auth=new SimpleGrantedAuthority(permissionValue);
authorities.add(auth);
}
return new UsernamePasswordAuthenticationToken(username,token,authorities);
} catch (ParseException e) {
e.printStackTrace();
} catch (JwtSignatureVerifyException e) {
e.printStackTrace();
} catch (JOSEException e) {
e.printStackTrace();
}
}
return null;
}
}
@Component
public class UserFileterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
private static Logger log = LoggerFactory.getLogger(UserFileterInvocationSecurityMetadataSource.class);
private AntPathMatcher antPathMatcher = new AntPathMatcher();
@Autowired
private MenuService menuService;
/**
* 返回本次访问需要的权限列表
* @param object
* @return
* @throws IllegalArgumentException
*/
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
log.info("进入了安全数据源");
String requestUrl = ((FilterInvocation) object).getRequestUrl();
//去数据库查询资源
List<ManMenu> allMenu = menuService.queryAllMenu();
for (ManMenu menu : allMenu) {
if (antPathMatcher.match(menu.getOpenAddress(), requestUrl)) {
List<ManRole> manRoles = menuService.queryAllMenuRelationRole(menu.getMenuId());
// List roles = menu.getRoles();
int size = manRoles.size();
String[] values = new String[size];
for (int i = 0; i < size; i++) {
values[i] = manRoles.get(i).getRoleName();
}
log.info("当前访问路径是{},这个url所需要的访问权限是{}", requestUrl,values);
return SecurityConfig.createList(values);
}
}
/**
* @Author: Galen
* @Description: 如果本方法返回null的话,意味着当前这个请求不需要任何角色就能访问
* 此处做逻辑控制,如果没有匹配上的,返回一个默认具体权限,防止漏缺资源配置
**/
log.info("当前访问路径是{},这个url所需要的访问权限是{}", requestUrl, "ROLE_LOGIN");
return SecurityConfig.createList("ROLE_LOGIN");
}
/**
* 此处方法如果做了实现,返回了定义的权限资源列表,
* Spring Security会在启动时校验每个ConfigAttribute是否配置正确,
* 如果不需要校验,这里实现方法,方法体直接返回null即可。
* @return
*/
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
/**
* 方法返回类对象是否支持校验,
* web项目一般使用FilterInvocation来判断,或者直接返回true
* @param clazz
* @return
*/
@Override
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
}
@Component
public class UserAccessDecisionManager implements AccessDecisionManager {
private static Logger log = LoggerFactory.getLogger(UserAccessDecisionManager.class);
@Autowired
private UserService userService;
/**
* 先查询此用户当前拥有的权限,然后与上面过滤器核查出来的权限列表作对比,
* 以此判断此用户是否具有这个访问权限,决定去留!所以顾名思义为权限决策器。
* @param authentication
* @param object
* @param configAttributes
* @throws AccessDeniedException
* @throws InsufficientAuthenticationException
*/
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
Iterator<ConfigAttribute> iterator = configAttributes.iterator();
while (iterator.hasNext()) {
if (authentication == null) {
throw new AccessDeniedException("当前访问没有权限");
}
ConfigAttribute ca = iterator.next();
//当前请求需要的权限
String needRole = ca.getAttribute();
if ("ROLE_LOGIN".equals(needRole)) {
if (authentication instanceof AnonymousAuthenticationToken) {
log.info("未登录");
throw new BadCredentialsException("未登录");
}
log.info("默认角色,没有权限");
throw new AccessDeniedException("权限不足!");
}
//当前用户所具有的权限
ManUser user = new ManUser();
String username = authentication.getPrincipal().toString();
user.setUsername(username);
ManUserVO manUserVO = userService.queryUserRelationAllRoleByUsername(user);
List<ManRole> roleList = manUserVO.getRoleList();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for (ManRole roleName : roleList) {
if (roleName.getRoleName().equals(needRole)) {
return;
}
}
}
log.info("权限不足!");
throw new AccessDeniedException("权限不足!");
}
@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
@Component
public class UnauthEntryPoint implements AccessDeniedHandler {
private static Logger log = LoggerFactory.getLogger(UnauthEntryPoint.class);
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
log.info("自定义未授权统一返回类 ======》 没有权限访问");
ResponseUtil.error(response, Result.result("403","no login",null));
}
}
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static Logger log = LoggerFactory.getLogger(SecurityConfig.class);
private JwtUtil jwtUtil;
private RedisUtils redisUtils;
private DefaultPasswordEncoderUtil defaultPasswordEncoderUtil;
private UserDetailsService userDetailsService;
/*
权限决策器
*/
@Autowired
private UserAccessDecisionManager userAccessDecisionManager;
/*
权限资源器
*/
@Autowired
private UserFileterInvocationSecurityMetadataSource userFileterInvocationSecurityMetadataSource;
/*
获取登录表单额外参数
*/
@Autowired
private AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> authenticationDetailsSource;
/*
未授权统一返回类
*/
@Autowired
private UnauthEntryPoint unauthEntryPoint;
@Autowired
public SecurityConfig(UserDetailsService userDetailsService,
DefaultPasswordEncoderUtil defaultPasswordEncoderUtil,
JwtUtil jwtUtil,
RedisUtils redisUtils) {
this.userDetailsService = userDetailsService;
this.defaultPasswordEncoderUtil = defaultPasswordEncoderUtil;
this.jwtUtil = jwtUtil;
this.redisUtils = redisUtils;
}
/**
* 配置设置,主要配置你的登录和权限控制、以及配置过滤器链。
*
* @param http
* @throws Exception
*/
//设置退出的地址和token,redis操作地址
@Override
protected void configure(HttpSecurity http) throws Exception {
log.info("进入了配置设置");
http.authorizeRequests().withObjectPostProcessor(
new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O o) {
o.setSecurityMetadataSource(userFileterInvocationSecurityMetadataSource);
o.setAccessDecisionManager(userAccessDecisionManager);
return o;
}
});
http.exceptionHandling().accessDeniedHandler(unauthEntryPoint)
//取消csrf防护
.and()
.csrf().disable()
//该配置是要求应用中所有url的访问都需要进行验证。我们也可以自定义哪些URL需要权限验证,哪些不需要
//所有请求都需要校验才能访问
.authorizeRequests().anyRequest().authenticated()
.and()
.cors()
//退出路径
.and()
.logout().logoutUrl("/admin/user/logout")
// 调用退出时的处理器
.addLogoutHandler(new TokenLogoutHandler( redisUtils,jwtUtil))
.and()
.formLogin()
.loginProcessingUrl("/admin/user/login").permitAll()
.successHandler(getLoginSuccessHandler())
.failureHandler(getLoginFailHandler())
.authenticationDetailsSource(authenticationDetailsSource)
.and()
// 认证过滤器
// .addFilter(new TokenLoginFilter(jwtUtil, redisUtils,authenticationManager()))
// 授权过滤器
.addFilter(new TokenAuthFilter(authenticationManager(),jwtUtil, redisUtils))
.httpBasic();
}
/**
* 调用userDetailsService和密码处理
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//使用SpringSecurity自己的校验规则,只能校验username和password
// auth.userDetailsService(userDetailsService).passwordEncoder(defaultPasswordEncoderUtil);
//使用自定义校验规则
auth.authenticationProvider(authenticationProvider());
}
/**
* 不进行认证的路径,可以直接访问,此时需要携带token,不然授权会抛出异常
* web.ignoring是直接绕开spring security的所有filter,直接跳过验证
* @param web
* @throws Exception
*/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/user/getImage")
.antMatchers("/user/code");
}
/**
* 自定义校验
*/
@Bean
public AuthenticationProvider authenticationProvider() {
LocalAuthenticationProvider authenticationProvider = new LocalAuthenticationProvider();
return authenticationProvider;
}
// 登录成功处理器
@Bean
public LoginSuccessHandler getLoginSuccessHandler() {
return new LoginSuccessHandler();
}
// 登录失败处理器
@Bean
public LoginErrorHandler getLoginFailHandler() {
return new LoginErrorHandler();
}
}
public class TokenLogoutHandler implements LogoutHandler {
private static Logger log = LoggerFactory.getLogger(TokenLogoutHandler.class);
private RedisUtils redisUtils;
private JwtUtil jwtUtil;
public TokenLogoutHandler(RedisUtils redisUtils, JwtUtil jwtUtil) {
this.redisUtils = redisUtils;
this.jwtUtil = jwtUtil;
}
@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
log.info("注销登录,拦截到了注销登录的请求");
//1、从header里面获取token
String token = request.getHeader("Authorization");
//2、token不为空,移除token,从Redis删除token
if(token != null){
try {
//解析token里的用户信息
PayloadDTO currentUser = jwtUtil.getJwtPayload();
log.info("解析后的用户数据为:"+currentUser.toString());
//将Redis里的用户信息及用户权限删除
redisUtils.remove(currentUser.getName());
} catch (ParseException e) {
e.printStackTrace();
} catch (JwtSignatureVerifyException e) {
e.printStackTrace();
} catch (JOSEException e) {
e.printStackTrace();
}
}
}
}
直接复制搜索就OK啦:https://www.processon.com/view/link/61b187daf346fb6a6f182ed9