认证授权过程分析
(1)如果是基于 Session,那么 Spring-security 会对 cookie 里的 sessionid 进行解析,找 到服务器存储的 session 信息,然后判断当前用户是否符合请求的要求。
(2)如果是 token,则是解析出 token,然后将当前请求加入到 Spring-security 管理的权限 信息中去
准备工作
目录结构
工具类
MD5加密工具
package com.wang.utils;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public final class MD5 {
public static String encrypt(String strSrc) {
try {
char hexChars[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
'9', 'a', 'b', 'c', 'd', 'e', 'f' };
byte[] bytes = strSrc.getBytes();
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(bytes);
bytes = md.digest();
int j = bytes.length;
char[] chars = new char[j * 2];
int k = 0;
for (int i = 0; i < bytes.length; i++) {
byte b = bytes[i];
chars[k++] = hexChars[b >>> 4 & 0xf];
chars[k++] = hexChars[b & 0xf];
}
return new String(chars);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
throw new RuntimeException("MD5加密出错!!+" + e);
}
}
public static void main(String[] args) {
System.out.println(MD5.encrypt("111111"));
}
}
统一结果返回值
package com.wang.utils;
public interface ResultCode {
public static Integer SUCCESS = 20000;
public static Integer ERROR = 20001;
}
package com.wang.utils;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.HashMap;
@Data
public class R {
@ApiModelProperty(value = "是否成功")
private Boolean success;
@ApiModelProperty(value = "返回码")
private Integer code;
@ApiModelProperty(value = "返回消息")
private String message;
@ApiModelProperty(value = "返回数据")
private java.util.Map<String, Object> data = new HashMap<String, Object>();
private R(){}
public static R ok(){
R r = new R();
r.setSuccess(true);
r.setCode(ResultCode.SUCCESS);
r.setMessage("成功");
return r;
}
public static R error(){
R r = new R();
r.setSuccess(false);
r.setCode(ResultCode.ERROR);
r.setMessage("失败");
return r;
}
public R success(Boolean success){
this.setSuccess(success);
return this;
}
public R message(String message){
this.setMessage(message);
return this;
}
public R code(Integer code){
this.setCode(code);
return this;
}
public R data(String key, Object value){
this.data.put(key, value);
return this;
}
public R data(java.util.Map<String, Object> map){
this.setData(map);
return this;
}
}
请求响应工具类
package com.wang;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wang.utils.R;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class ResponseUtil {
public static void out(HttpServletResponse response, R r) {
ObjectMapper mapper = new ObjectMapper();
response.setStatus(HttpStatus.OK.value());
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
try {
mapper.writeValue(response.getWriter(), r);
} catch (IOException e) {
e.printStackTrace();
}
}
}
全局异常处理
package com.wang.exceptionhandler;
import com.wang.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
@ResponseBody
public R error(Exception e){
e.printStackTrace();
return R.error().message("执行了全局异常!!!");
}
@ExceptionHandler(ArithmeticException.class)
@ResponseBody
public R error(ArithmeticException e){
e.printStackTrace();
return R.error().message("执行了ArithmeticException异常!!!");
}
@ExceptionHandler(WangException.class)
@ResponseBody
public R error(WangException e){
log.error(e.getMessage());
e.printStackTrace();
return R.error().code(e.getCode()).message(e.getMsg());
}
}
自定义异常处理
package com.wang.exceptionhandler;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class WangException extends RuntimeException{
@ApiModelProperty(value = "状态码")
private Integer code;
private String msg;
}
swagger测试
package com.wang;
import com.google.common.base.Predicates;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration//配置类
@EnableSwagger2 //swagger注解
public class SwaggerConfig {
@Bean
public Docket webApiConfig(){
return new Docket(DocumentationType.SWAGGER_2)
.groupName("webApi")
.apiInfo(webApiInfo())
.select()
//.paths(Predicates.not(PathSelectors.regex("/admin/.*")))
.paths(Predicates.not(PathSelectors.regex("/error.*")))
.build();
}
private ApiInfo webApiInfo(){
return new ApiInfoBuilder()
.title("网站-课程中心API文档")
.description("本文档描述了课程中心微服务接口定义")
.version("1.0")
.contact(new Contact("java", "http://wang.com", "1123@qq.com"))
.build();
}
}
redis配置
package com.wang;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setConnectionFactory(factory);
//key序列化方式
template.setKeySerializer(redisSerializer);
//value序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
//value hashmap序列化
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题),过期时间600秒
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
}
准备工作结束
springsecurity需要的一些类
目录结构
密码处理的方法
package com.wang.security.security;
import com.wang.utils.MD5;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
//密码处理的方法
@Component
public class DefaultPasswordEncoder implements PasswordEncoder {
public DefaultPasswordEncoder() {
this(-1);
}
public DefaultPasswordEncoder(int a) {
}
@Override
public String encode(CharSequence charSequence) {
return MD5.encrypt(charSequence.toString());
}
@Override
public boolean matches(CharSequence charSequence, String encodepassword) {
return encodepassword.equals(MD5.encrypt(charSequence.toString()));
}
}
退出处理类
package com.wang.security.security;
import com.wang.ResponseUtil;
import com.wang.utils.R;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class TokenLogoutHandler implements LogoutHandler {
private TokenManager tokenManager;
private RedisTemplate redisTemplate;
public TokenLogoutHandler(TokenManager tokenManager,RedisTemplate redisTemplate ){
this.tokenManager=tokenManager;
this.redisTemplate=redisTemplate;
}
@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
String token = request.getHeader("token");
if (token!=null)
{
String username = tokenManager.getUserFromToken(token);
redisTemplate.delete(token);
}
ResponseUtil.out(response, R.ok());
}
}
token 操作的工具类
package com.wang.security.security;
import io.jsonwebtoken.CompressionCodec;
import io.jsonwebtoken.CompressionCodecs;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;
import java.util.Date;
//token 操作的工具类
@Component
public class TokenManager {
private long tokenExpiration=1000*60*60*24;//设置过期时间
private String tokenSignKey="123456";//加密字符串
//生成token
private String createToken(String username){
String token = Jwts.builder().setSubject(username).setExpiration(new Date(System.currentTimeMillis() + tokenExpiration)
).signWith(SignatureAlgorithm.HS512, tokenSignKey).compressWith(CompressionCodecs.GZIP).compact();
return token;
}
//根据token得到用户信息
public String getUserFromToken(String token)
{
String user = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody().getSubject();
return user;
}
}
未授权统一处理
package com.wang.security.security;
import com.wang.ResponseUtil;
import com.wang.utils.R;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
//未授权统一处理
public class UnauthorizedEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
ResponseUtil.out(httpServletResponse, R.error());
}
}
正式开始
spring接收前端信息的实体类
package com.wang.security.entity;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import java.io.Serializable;
@Data
@ApiModel(description = "用户实体类")
public class User implements Serializable {
private String username;
private String password;
private String nickName;
private String salt;
private String token;
}
springsecurity需要接管的实现类UserDetails的实体对象 securityUser,用来封装用户请求信息和权限
package com.wang.security.entity;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* <p>
* 安全认证用户详情信息
* </p>
*
* @author qy
* @since 2019-11-08
*/
@Data
@Slf4j
public class SecurityUser implements UserDetails {
//当前登录用户
private transient User currentUserInfo;
//当前权限
private List<String> permissionValueList;
public SecurityUser() {
}
public SecurityUser(User user) {
if (user != null) {
this.currentUserInfo = user;
}
}
@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;
}
@Override
public String getPassword() {
return currentUserInfo.getPassword();
}
@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;
}
}
认证过程
源码分析
1.
2.查看过滤器的父类AbstractAuthenticationProcessingFilter
上述的 第二 过程调用了 UsernamePasswordAuthenticationFilter 的 attemptAuthentication() 方法,源码如下:
上述的(3)过程创建的 UsernamePasswordAuthenticationToken 是 Authentication 接口的实现类,该类有两个构造器,一个用于封装前端请求传入的未认 证的用户信息,一个用于封装认证成功后的用户信息:
Authentication 接口的实现类用于存储用户认证信息,查看该接口具体定义:
ProviderManager 源码
上述过程中,UsernamePasswordAuthenticationFilter 过滤器的 attemptAuthentication() 方法的(5)过程将未认证的 Authentication 对象传入 ProviderManager 类的 authenticate() 方法进行身份认证。ProviderManager 是 AuthenticationManager 接口的实现类,该接口是认证相关的核心接 口,也是认证的入口。在实际开发中,我们可能有多种不同的认证方式,例如:用户名+ 密码、邮箱+密码、手机号+验证码等,而这些认证方式的入口始终只有一个,那就是 AuthenticationManager。在该接口的常用实现类 ProviderManager 内部会维护一个 List列表,存放多种认证方式,实际上这是委托者模式 (Delegate)的应用。每种认证方式对应着一个 AuthenticationProvider, AuthenticationManager 根据认证方式的不同(根据传入的 Authentication 类型判断)委托 对应的 AuthenticationProvider 进行用户认证。
代码实现
1.首先进入TokenLoginFilter(继承了UsernamePasswordAuthenticationFilter)的attemptAuthentication()方法进行信息认证,需要一个实现了UserDetails的实体对象,在UserDetailsServiceImpl的loadUserByUsername方法返回值可以得到,通过实现了UserDetails的实体对象 securityUser可以将前端传过来的用户信息与数据库查到的用户信息比较
package com.wang.security.filter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wang.ResponseUtil;
import com.wang.security.entity.SecurityUser;
import com.wang.security.entity.User;
import com.wang.security.security.TokenManager;
import com.wang.utils.R;
import io.jsonwebtoken.Jwts;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
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;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {
private TokenManager tokenManager;
private RedisTemplate redisTemplate;
private AuthenticationManager authenticationManager;//spring来验证用户信息
public TokenLoginFilter(TokenManager tokenManager, RedisTemplate redisTemplate, AuthenticationManager authenticationManager) {
this.tokenManager = tokenManager;
this.redisTemplate = redisTemplate;
this.authenticationManager = authenticationManager;
this.setPostOnly(false);
this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/acl/login","POST"));
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
try {
User user = new ObjectMapper().readValue(request.getInputStream(), User.class);//获取前端请求的对象
return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(),user.getPassword(),new ArrayList<>()));//封装用户信息
//实际上做这么多就是要把用户的信息和权限封装成Authentication,然后保存到securitycontext中
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException();
}
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
//认证成功,等得到认证成功之后的用户信息
SecurityUser user = (SecurityUser) authResult.getPrincipal();
String token = tokenManager.getUserFromToken(user.getUsername());
redisTemplate.opsForValue().set(user.getCurrentUserInfo().getUsername(),user.getPermissionValueList());
ResponseUtil.out(response, R.ok().data("token",token));
}
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
ResponseUtil.out(response, R.error());
}
}
2.从权限认证模块UserDetailsServiceImpl的loadUserByUsername方法获取用户登录的信息,封在UserDetails的实现类中的,把信息封装成springsecurity需要的user类型,设置给UserDetails的实现类securityUser,再查询到权限信息,设置给UserDetails的实现类securityUser
package com.wang.aclservice.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.wang.aclservice.entity.User;
import com.wang.aclservice.service.PermissionService;
import com.wang.aclservice.service.UserService;
import com.wang.security.entity.SecurityUser;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.List;
import javax.annotation.Resource;
import java.util.List;
/**
* @author lixiaolong
*/
@Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {
@Resource
private UserService userService;
@Resource
private PermissionService permissionService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//根据用户名查询数据
User user = userService.selectByUsername(username);
//判断
if (user == null) {
throw new UsernameNotFoundException("用户不存在");
}
com.wang.security.entity.User curUser = new com.wang.security.entity.User();//查询出来的对象赋值给security的对象,spring要认证
BeanUtils.copyProperties(user, curUser);//保持请求的类型与security要接受的一致,security接受的是自定义的
//根据用户查询用户权限列表
List<String> permissionValueList = permissionService.selectPermissionValueByUserId(user.getId());
SecurityUser securityUser = new SecurityUser();//curUser
securityUser.setCurrentUserInfo(curUser);
securityUser.setPermissionValueList(permissionValueList);//获取用户权限 设置给security,返回UserDetails实现类 交给springsecurity
return securityUser;
}
}
3.认证成功之后到进入TokenLoginFilter的successfulAuthentication()方法得到用户的当前信息(包括在第2步中设置的用户信息和权限信息),然后生成token,将用户名和权限以key:value设置到redis中,将token返回前端
认证完毕
package com.wang.security.filter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wang.ResponseUtil;
import com.wang.security.entity.SecurityUser;
import com.wang.security.entity.User;
import com.wang.security.security.TokenManager;
import com.wang.utils.R;
import io.jsonwebtoken.Jwts;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
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;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {
private TokenManager tokenManager;
private RedisTemplate redisTemplate;
private AuthenticationManager authenticationManager;//spring来验证用户信息
public TokenLoginFilter(TokenManager tokenManager, RedisTemplate redisTemplate, AuthenticationManager authenticationManager) {
this.tokenManager = tokenManager;
this.redisTemplate = redisTemplate;
this.authenticationManager = authenticationManager;
this.setPostOnly(false);
this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/acl/login","POST"));
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
try {
User user = new ObjectMapper().readValue(request.getInputStream(), User.class);//获取前端请求的对象
return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(),user.getPassword(),new ArrayList<>()));//封装用户信息
//实际上做这么多就是要把用户的信息和权限封装成Authentication,然后保存到securitycontext中
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException();
}
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
//认证成功,等得到认证成功之后的用户信息
SecurityUser user = (SecurityUser) authResult.getPrincipal();
String token = tokenManager.getUserFromToken(user.getUsername());
redisTemplate.opsForValue().set(user.getCurrentUserInfo().getUsername(),user.getPermissionValueList());
ResponseUtil.out(response, R.ok().data("token",token));
}
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
ResponseUtil.out(response, R.error());
}
}
授权
SpringSecurity 权限访问流程
ExceptionTranslationFilter 过滤器 该过滤器是用于处理异常的,不需要我们配置,对于前端提交的请求会直接放行,捕获后 续抛出的异常并进行处理(例如:权限访问限制)。具体源码如下:
FilterSecurityInterceptor 过滤器 FilterSecurityInterceptor 是过滤器链的最后一个过滤器,该过滤器是过滤器链 的最后一个过滤器,根据资源权限配置来判断当前请求是否有权限访问对应的资源。如果 访问受限会抛出相关异常,最终所抛出的异常会由前一个过滤器 ExceptionTranslationFilter 进行捕获和处理。具体源码如下
需要注意,Spring Security 的过滤器链是配置在 SpringMVC 的核心组件 DispatcherServlet 运行之前。也就是说,请求通过 Spring Security 的所有过滤器, 不意味着能够正常访问资源,该请求还需要通过 SpringMVC 的拦截器链。
1.从前端head中得到token信息,然后用jwt根据token获取用户名 再从redis中查询权限,给当前用户授权查到的权限信息
package com.wang.security.filter;
import com.wang.ResponseUtil;
import com.wang.security.security.TokenManager;
import com.wang.utils.R;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.util.StringUtils;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class TokenAuthFilter extends BasicAuthenticationFilter {
private TokenManager tokenManager;
private RedisTemplate redisTemplate;
public TokenAuthFilter(AuthenticationManager authenticationManager, TokenManager tokenManager,RedisTemplate redisTemplate) {
super(authenticationManager);
this.tokenManager=tokenManager;
this.redisTemplate=redisTemplate;
}
@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
logger.info("================="+req.getRequestURI());
if(req.getRequestURI().indexOf("admin") == -1) {
chain.doFilter(req, res);
return;
}
UsernamePasswordAuthenticationToken authentication = null;
try {
authentication = getAuthentication(req);
} catch (Exception e) {
ResponseUtil.out(res, R.error());
}
if (authentication != null) {
SecurityContextHolder.getContext().setAuthentication(authentication);
//实际上做这么多就是要把用户的信息和权限封装成Authentication,然后保存到securitycontext中
} else {
ResponseUtil.out(res, R.error());
}
chain.doFilter(req, res);
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
// token置于header里
String token = request.getHeader("token");
if (token != null && !"".equals(token.trim())) {
String userName = tokenManager.getUserFromToken(token);
List<String> permissionValueList = (List<String>) redisTemplate.opsForValue().get(userName);
Collection<GrantedAuthority> authorities = new ArrayList<>();
for(String permissionValue : permissionValueList) {
if(StringUtils.isEmpty(permissionValue)) continue;
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue);
authorities.add(authority);
}
if (!StringUtils.isEmpty(userName)) {
return new UsernamePasswordAuthenticationToken(userName, token, authorities);
}
return null;
}
return null;
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!