IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Springboot最全权限集成Redis-前后端分离-springsecurity-jwt-Token -> 正文阅读

[Java知识库]Springboot最全权限集成Redis-前后端分离-springsecurity-jwt-Token

项目码云:?https://gitee.com/kewen-yang/back2.git

1.Spring Security介绍:

????????Spring Security是基于Spring生态圈的,用于提供安全访问控制解决方案的框架。Spring Security的安 全管理有两个重要概念,分别是Authentication(认证)和Authorization(授权)。

????????为了方便Spring Boot项目的安全管理,Spring Boot对Spring Security安全框架进行了整合支持,并提 供了通用的自动化配置,从而实现了Spring Security安全框架中包含的多数安全管理功能。

????????Spring Security登录认证主要涉及两个重要的接口 UserDetailServiceUserDetails接口。 UserDetailService接口主要定义了一个方法 loadUserByUsername(String username)用于完成用户信息的查 询,其中username就是登录时的登录名称,登录认证时,需要自定义一个实现类实现UserDetailService接 口,完成数据库查询,该接口返回UserDetail。

????????UserDetail主要用于封装认证成功时的用户信息,即UserDetailService返回的用户信息,可以用Spring 自己的User对象,但是最好是实现UserDetail接口,自定义用户对象。

2.Spring Security认证步骤:

????????1. 自定UserDetails类:当实体对象字段不满足时需要自定义UserDetails,一般都要自定义 UserDetails。

????????2. 自定义UserDetailsService类,主要用于从数据库查询用户信息。

????????3. 创建登录认证成功处理器,认证成功后需要返回JSON数据,菜单权限等。

????????4. 创建登录认证失败处理器,认证失败需要返回JSON数据,给前端判断。

????????5. 创建匿名用户访问无权限资源时处理器,匿名用户访问时,需要提示JSON。

????????6. 创建认证过的用户访问无权限资源时的处理器,无权限访问时,需要提示JSON。

????????7. 配置Spring Security配置类,把上面自定义的处理器交给Spring Security。

3.Spring Security认证实现:

????????3.3.1 添加Spring Security依赖:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

????????3.3.2 自定义UserDetails类:

????????当实体对象字段不满足时Spring Security认证时,需要自定义UserDetails。

? ? ? ? 3.3.3 注意点:

? ? ? ? ????????3.3.3.1. 将User类实现UserDetails接口

? ? ? ? ????????3.3.3.2. 将原有的isAccountNonExpired、isAccountNonLocked,isCredentialsNonExpired和isEnabled属性修 改成boolean类型,同时添加authorities属性。你可以点击userdetail去看看,里面都是boolean类型,不是Boolean类型的。

注意:这五个是集成spring security必须要用到的UserDetails类几个属性.

@Data
@TableName("sys_user")
public class User implements Serializable, UserDetails {
//省略原有的属性......
/**
* 帐户是否过期(1 未过期,0已过期)
*/
private boolean isAccountNonExpired = true;
/**
* 帐户是否被锁定(1 未过期,0已过期)
*/
private boolean isAccountNonLocked = true;
/**
* 密码是否过期(1 未过期,0已过期)
*/
private boolean isCredentialsNonExpired = true;
/**
* 帐户是否可用(1 可用,0 删除用户)
*/
private boolean isEnabled = true;
/**
* 权限列表
*/
@TableField(exist = false)
Collection<? extends GrantedAuthority> authorities;
}

????????实际项目开发的完整user实体类:

@TableName(value ="sys_user")
@Data
public class User implements Serializable , UserDetails {
    /**
     * 用户编号
     */
    @TableId(type = IdType.AUTO)
    private Long id;

    /**
     * 登录名称(用户名)
     */
    private String username;

    /**
     * 登录密码
     */
    private String password;

    /**
     * 帐户是否过期(1-未过期,0-已过期)
     */
    private boolean isAccountNonExpired = true;

    /**
     * 帐户是否被锁定(1-未过期,0-已过期)
     */
    private boolean isAccountNonLocked = true;

    /**
     * 密码是否过期(1-未过期,0-已过期)
     */
    private boolean isCredentialsNonExpired =true;

    /**
     * 帐户是否可用(1-可用,0-禁用)
     */
    private boolean isEnabled =true;

    /**
     * 真实姓名
     */
    private String realName;

    /**
     * 昵称
     */
    private String nickName;

    /**
     * 所属部门ID
     */
    private Long departmentId;

    /**
     * 所属部门名称
     */
    private String departmentName;

    /**
     * 性别(0-男,1-女)
     */
    private Integer gender;

    /**
     * 电话
     */
    private String phone;

    /**
     * 邮箱
     */
    private String email;

    /**
     * 用户头像
     */
    private String avatar;

    /**
     * 是否是管理员(1-管理员)
     */
    private Integer isAdmin;

    /**
     * 创建时间
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date createTime;

    /**
     * 修改时间
     */
    @TableField(fill = FieldFill.UPDATE)
    private Date updateTime;

    /**
     * 是否删除(0-未删除,1-已删除)
     */
    private Integer isDelete;
    /**
     * 权限列表
     */
    @TableField(exist = false)
    Collection<? extends GrantedAuthority> authorities;
    /**
     * 查询用户权限列表
     */
    @TableField(exist = false)
    private List<Permission> permissionList;


    @TableField(exist = false)
    private static final long serialVersionUID = 1L;

    @Override
    public boolean equals(Object that) {
        if (this == that) {
            return true;
        }
        if (that == null) {
            return false;
        }
        if (getClass() != that.getClass()) {
            return false;
        }
        User other = (User) that;
        return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
            && (this.getUsername() == null ? other.getUsername() == null : this.getUsername().equals(other.getUsername()))
            && (this.getPassword() == null ? other.getPassword() == null : this.getPassword().equals(other.getPassword()))
            && (this.getRealName() == null ? other.getRealName() == null : this.getRealName().equals(other.getRealName()))
            && (this.getNickName() == null ? other.getNickName() == null : this.getNickName().equals(other.getNickName()))
            && (this.getDepartmentId() == null ? other.getDepartmentId() == null : this.getDepartmentId().equals(other.getDepartmentId()))
            && (this.getDepartmentName() == null ? other.getDepartmentName() == null : this.getDepartmentName().equals(other.getDepartmentName()))
            && (this.getGender() == null ? other.getGender() == null : this.getGender().equals(other.getGender()))
            && (this.getPhone() == null ? other.getPhone() == null : this.getPhone().equals(other.getPhone()))
            && (this.getEmail() == null ? other.getEmail() == null : this.getEmail().equals(other.getEmail()))
            && (this.getAvatar() == null ? other.getAvatar() == null : this.getAvatar().equals(other.getAvatar()))
            && (this.getIsAdmin() == null ? other.getIsAdmin() == null : this.getIsAdmin().equals(other.getIsAdmin()))
            && (this.getCreateTime() == null ? other.getCreateTime() == null : this.getCreateTime().equals(other.getCreateTime()))
            && (this.getUpdateTime() == null ? other.getUpdateTime() == null : this.getUpdateTime().equals(other.getUpdateTime()))
            && (this.getIsDelete() == null ? other.getIsDelete() == null : this.getIsDelete().equals(other.getIsDelete()));
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
        result = prime * result + ((getUsername() == null) ? 0 : getUsername().hashCode());
        result = prime * result + ((getPassword() == null) ? 0 : getPassword().hashCode());
        result = prime * result + ((getRealName() == null) ? 0 : getRealName().hashCode());
        result = prime * result + ((getNickName() == null) ? 0 : getNickName().hashCode());
        result = prime * result + ((getDepartmentId() == null) ? 0 : getDepartmentId().hashCode());
        result = prime * result + ((getDepartmentName() == null) ? 0 : getDepartmentName().hashCode());
        result = prime * result + ((getGender() == null) ? 0 : getGender().hashCode());
        result = prime * result + ((getPhone() == null) ? 0 : getPhone().hashCode());
        result = prime * result + ((getEmail() == null) ? 0 : getEmail().hashCode());
        result = prime * result + ((getAvatar() == null) ? 0 : getAvatar().hashCode());
        result = prime * result + ((getIsAdmin() == null) ? 0 : getIsAdmin().hashCode());
        result = prime * result + ((getCreateTime() == null) ? 0 : getCreateTime().hashCode());
        result = prime * result + ((getUpdateTime() == null) ? 0 : getUpdateTime().hashCode());
        result = prime * result + ((getIsDelete() == null) ? 0 : getIsDelete().hashCode());
        return result;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(getClass().getSimpleName());
        sb.append(" [");
        sb.append("Hash = ").append(hashCode());
        sb.append(", id=").append(id);
        sb.append(", username=").append(username);
        sb.append(", password=").append(password);
        sb.append(", isAccountNonExpired=").append(isAccountNonExpired);
        sb.append(", isAccountNonLocked=").append(isAccountNonLocked);
        sb.append(", isCredentialsNonExpired=").append(isCredentialsNonExpired);
        sb.append(", isEnabled=").append(isEnabled);
        sb.append(", realName=").append(realName);
        sb.append(", nickName=").append(nickName);
        sb.append(", departmentId=").append(departmentId);
        sb.append(", departmentName=").append(departmentName);
        sb.append(", gender=").append(gender);
        sb.append(", phone=").append(phone);
        sb.append(", email=").append(email);
        sb.append(", avatar=").append(avatar);
        sb.append(", isAdmin=").append(isAdmin);
        sb.append(", createTime=").append(createTime);
        sb.append(", updateTime=").append(updateTime);
        sb.append(", isDelete=").append(isDelete);
        sb.append(", serialVersionUID=").append(serialVersionUID);
        sb.append("]");
        return sb.toString();
    }
}

? ? 3.3.4:编写UserService接口? ? ?编写 根据用户名查询用户信息 的方法。

public interface UserService extends IService<User> {
/**
* 根据用户名查询用户信息
* @param userName
* @return
*/
User findUserByUserName(String userName);
}

3.3.5:编写UserService接口实现类?

@Service
@Transactional
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements
UserService {
/**
* 根据用户名查询用户信息
*
* @param userName
* @return
*/
@Override
public User findUserByUserName(String userName) {
//创建条件构造器
QueryWrapper<User> queryWrapper = new QueryWrapper<User>();
//用户名
queryWrapper.eq("username",userName);
//返回查询记录
return baseMapper.selectOne(queryWrapper);
}
}

3.3.6?自定义UserDetailsService类

????????创建CustomerUserDetailsService用户认证处理类,该类需要实 现UserDetailsService接口。

@Component
public class CustomerUserDetailsService implements UserDetailsService {
@Resource
private UserService userService;
@Override
public UserDetails loadUserByUsername(String username) throws
UsernameNotFoundException {
//调用根据用户名查询用户信息的方法
User user = userService.findUserByUserName(username);
//如果对象为空,则认证失败
if (user == null) {
throw new UsernameNotFoundException("用户名或密码错误!");
}
return user;
}
}

3.3.7:编写自定义认证成功处理器

? ? ? ? 要实现一个接口AuthenticationSuccessHandler

/**
* 登录认证成功处理器类
*/
@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication) throws IOException,
ServletException {
//设置客户端的响应的内容类型
response.setContentType("application/json;charset=UTF-8");
//获取当登录用户信息
User user = (User) authentication.getPrincipal();
//消除循环引用
String result = JSON.toJSONString(user,
SerializerFeature.DisableCircularReferenceDetect);
//获取输出流
ServletOutputStream outputStream = response.getOutputStream();
outputStream.write(result.getBytes(StandardCharsets.UTF_8));
outputStream.flush();
outputStream.close();
}
}

3.3.8:编写自定义认证失败处理器

? ? ? ? 实现一个接口AuthenticationFailureHandler

/**
* 用户认证失败处理类
*/
@Component
public class LoginFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response, AuthenticationException exception) throws
IOException, ServletException {
//设置客户端响应编码格式
response.setContentType("application/json;charset=UTF-8");
//获取输出流
ServletOutputStream outputStream = response.getOutputStream();
String message = null;//提示信息
int code = 500;//错误编码
//判断异常类型
if(exception instanceof AccountExpiredException){
message = "账户过期,登录失败!";
}else if(exception instanceof BadCredentialsException){
message = "用户名或密码错误,登录失败!";
}else if(exception instanceof CredentialsExpiredException){
message = "密码过期,登录失败!";
}else if(exception instanceof DisabledException){
message = "账户被禁用,登录失败!";
}else if(exception instanceof LockedException){
message = "账户被锁,登录失败!";
}else if(exception instanceof InternalAuthenticationServiceException){
message = "账户不存在,登录失败!";
}else{
message = "登录失败!";
}
//将错误信息转换成JSON
String result =
JSON.toJSONString(Result.error().code(code).message(message));
outputStream.write(result.getBytes(StandardCharsets.UTF_8));
outputStream.flush();
outputStream.close();
}
}

3.3.9:编写认证用户无权限访问处理器

? ? ? ? 实现接口AccessDeniedHandler

/**
* 认证用户访问无权限资源时处理器
*/
@Component
public class CustomerAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException
{
//设置客户端的响应的内容类型
response.setContentType("application/json;charset=UTF-8");
//获取输出流
ServletOutputStream outputStream = response.getOutputStream();
//消除循环引用
String result = JSON.toJSONString(Result.error().code(700).message("无权限
访问,请联系管理员!"), SerializerFeature.DisableCircularReferenceDetect);
outputStream.write(result.getBytes(StandardCharsets.UTF_8));
outputStream.flush();
outputStream.close();
}
}

3.3.10:编写匿名用户访问资源处理器

? ? ? ? 实现接口AuthenticationEntryPoint

@Component
public class AnonymousAuthenticationHandler implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse
response, AuthenticationException authException) throws IOException,
ServletException {
//设置客户端的响应的内容类型
response.setContentType("application/json;charset=UTF-8");
//获取输出流
ServletOutputStream outputStream = response.getOutputStream();
//消除循环引用
String result = JSON.toJSONString(Result.error().code(600).message("匿名用
户无权限访问!"), SerializerFeature.DisableCircularReferenceDetect);
outputStream.write(result.getBytes(StandardCharsets.UTF_8));
outputStream.flush();
outputStream.close();
}
}

3.3.11:编写Spring Security配置类

? ? ? ? 需要继承WebSecurityConfigurerAdapter

????????@EnableWebSecurity:开启springsecurity

@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private CustomerUserDetailsService customerUserDetailsService;
@Resource
private LoginSuccessHandler loginSuccessHandler;
@Resource
private LoginFailureHandler loginFailureHandler;
@Resource
private AnonymousAuthenticationHandler anonymousAuthenticationHandler;
@Resource
private CustomerAccessDeniedHandler customerAccessDeniedHandler;
/**
* 注入加密处理类
*
* @return
*/
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//登录前进行过滤
http.formLogin()
.loginProcessingUrl("/api/user/login")
// 设置登录验证成功或失败后的的跳转地址
.successHandler(loginSuccessHandler).failureHandler(loginFailureHandler)
// 禁用csrf防御机制
.and().csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
//关闭session
.and()
.authorizeRequests()//设置拦截请求
.antMatchers("/api/user/login").permitAll()//登录请求放行
.anyRequest().authenticated()//其他请求都要拦截
.and()
.exceptionHandling()
.authenticationEntryPoint(anonymousAuthenticationHandler)//匿名用户无权限访问
.accessDeniedHandler(customerAccessDeniedHandler)//认证用户无权限访问
.and().cors();//开启跨域配置
}
/**
* 配置认证处理器
*
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
{
auth.userDetailsService(customerUserDetailsService).passwordEncoder(passwordEnco
der());
}
}

3.3.11 测试登录认证接口

3.4 认证成功返回token

????????3.4.3 认证成功处理器返回token信息

package com.manong.utils;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginResult {
//用户编号
private Long id;
//状态码
private int code;
//token令牌
private String token;
//token过期时间
private Long expireTime;
}

????????编写token工具类:

@Data
@ConfigurationProperties(prefix = "jwt")
@Component
public class JwtUtils {
//密钥
private String secret;
// 过期时间 毫秒
private Long expiration;
/**
* 从数据声明生成令牌
*
* @param claims 数据声明
* @return 令牌
*/
private String generateToken(Map<String, Object> claims) {
Date expirationDate = new Date(System.currentTimeMillis() + expiration);
return
Jwts.builder().setClaims(claims).setExpiration(expirationDate).signWith(Signatur
eAlgorithm.HS512, secret).compact();
}
/**
* 从令牌中获取数据声明
*
* @param token 令牌
* @return 数据声明
*/
public Claims getClaimsFromToken(String token) {
Claims claims;
try {
claims =
Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
} catch (Exception e) {
claims = null;
}
return claims;
}
/**
* 生成令牌
*
* @param userDetails 用户
* @return 令牌
*/
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>(2);
claims.put(Claims.SUBJECT, userDetails.getUsername());
claims.put(Claims.ISSUED_AT, new Date());
return generateToken(claims);
}
/**
* 从令牌中获取用户名
*
* @param token 令牌
* @return 用户名
*/
public String getUsernameFromToken(String token) {
String username;
try {
Claims claims = getClaimsFromToken(token);
username = claims.getSubject();
} catch (Exception e) {
username = null;
}
return username;
}
/**
* 判断令牌是否过期
*
* @param token 令牌
* @return 是否过期
*/
public Boolean isTokenExpired(String token) {
Claims claims = getClaimsFromToken(token);
Date expiration = claims.getExpiration();
return expiration.before(new Date());
}
/**
* 刷新令牌
*
* @param token 原令牌
* @return 新令牌
*/
public String refreshToken(String token) {
String refreshedToken;
try {
Claims claims = getClaimsFromToken(token);
claims.put(Claims.ISSUED_AT, new Date());
refreshedToken = generateToken(claims);
} catch (Exception e) {
refreshedToken = null;
}
return refreshedToken;
}
/**
* 验证令牌
*
* @param token 令牌
* @param userDetails 用户
* @return 是否有效
*/
public Boolean validateToken(String token, UserDetails userDetails) {
User user = (User) userDetails;
String username = getUsernameFromToken(token);
return (username.equals(user.getUsername()) && !isTokenExpired(token));
}
}

(3)编写全局配置文件

????????????????在application.properties全局配置文件中自定义jwt属性。

#jwt配置
#密钥
jwt.secret=com.manong
#过期时间
jwt.expiration=1800000

(4)认证成功处理器类返回token数据 在原有的LoginSuccessHandler登录认证成功处理器类上加入jwt相关代码。 注意:在原有的基础上添加

/**
* 登录认证成功处理器类
*/
@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
@Resource
private JwtUtils jwtUtils;
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication) throws IOException,
ServletException {
//设置客户端的响应的内容类型
response.setContentType("application/json;charset=UTF-8");
//获取当登录用户信息
User user = (User) authentication.getPrincipal();
//生成token
String token = jwtUtils.generateToken(user);
//设置token签名密钥及过期时间
long expireTime = Jwts.parser() //获取DefaultJwtParser对象
.setSigningKey(jwtUtils.getSecret()) //设置签名的密钥
.parseClaimsJws(token.replace("jwt_", ""))
.getBody().getExpiration().getTime();//获取token过期时间
//创建登录结果对象
LoginResult loginResult = new LoginResult(user.getId(),
ResultCode.SUCCESS,token,expireTime);
//消除循环引用
String result = JSON.toJSONString(loginResult,
SerializerFeature.DisableCircularReferenceDetect);
//获取输出流
ServletOutputStream outputStream = response.getOutputStream();
outputStream.write(result.getBytes(StandardCharsets.UTF_8));
outputStream.flush();
outputStream.close();
}
}

3.6 token验证

????????3.6.1 添加Redis依赖

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

3.6.2 编写全局配置文件

################################# Redis相关配置 #################################
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.database=0
spring.redis.timeout=10000
#自定义属性
spring.redis.expire=60000

3.6.3 编写Redis配置类

????????

/**
* redis配置类
*/
@Configuration
public class RedisConfig {
//缓存过期时间
@Value("${spring.redis.expire}")
private Long expire;
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory
factory) {
RedisTemplate<String, Object> template = new RedisTemplate<String,
Object>();
// 配置连接工厂
template.setConnectionFactory(factory);
//使用Jackson2JsonRedisSerialize 替换默认序列化(默认采用的是JDK序列化)
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new
Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
//设置在生成 json 串时,对象中的成员的可见性
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
//存储到redis的json将是有类型的数据
//指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等
会跑出异常
om.activateDefaultTyping(om.getPolymorphicTypeValidator(),
ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
StringRedisSerializer stringRedisSerializer = new
StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
//@Cacheable注解字符集编码配置
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config =
RedisCacheConfiguration.defaultCacheConfig();
config.entryTtl(Duration.ofMinutes(expire))//缓存过期时间
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(Red
isSerializer.string()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(R
edisSerializer.json()));
return RedisCacheManager
.builder(factory)
.cacheDefaults(config)
.build();
}
}

(2)编写RedisService业务工具类

????????

@Component
public class RedisService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
//存缓存
public void set(String key ,String value,Long timeOut){
redisTemplate.opsForValue().set(key,value,timeOut, TimeUnit.SECONDS);
}
//取缓存
public String get(String key){
return (String) redisTemplate.opsForValue().get(key);
}
//清除缓存
public void del(String key){
redisTemplate.delete(key);
}
}

3.6.4 设置登录认证请求地址 在application.properties全局配置文件中自定义登录验证的请求地址。

#登录请求地址(自定义)
request.login.url=/api/user/login

3.6.5 编写token验证过滤器类

/**
* token验证过滤器
*/
@Data
@Component
public class CheckTokenFilter extends OncePerRequestFilter {
@Resource
private JwtUtils jwtUtils;
@Resource
private CustomerUserDetailsService customerUserDetailsService;
@Resource
private LoginFailureHandler loginFailureHandler;
@Resource
private RedisService redisService;
//获取登录请求地址
@Value("${request.login.url}")
private String loginUrl;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain) throws ServletException,
IOException {
try {
//获取当前请求的url地址
String url = request.getRequestURI();
//如果当前请求不是登录请求,则需要进行token验证
if (!url.equals(loginUrl)) {
this.validateToken(request);
}
} catch (AuthenticationException e) {
loginFailureHandler.onAuthenticationFailure(request, response, e);
}
//登录请求不需要验证token
doFilter(request, response, filterChain);
}
/**
* 验证token
*
* @param request
*/
private void validateToken(HttpServletRequest request) throws
AuthenticationException {
//从头部获取token信息
String token = request.getHeader("token");
//如果请求头部没有获取到token,则从请求的参数中进行获取
if (ObjectUtils.isEmpty(token)) {
token = request.getParameter("token");
}
//如果请求参数中也不存在token信息,则抛出异常
if (ObjectUtils.isEmpty(token)) {
throw new CustomerAuthenticationException("token不存在");
}
//判断redis中是否存在该token
String tokenKey = "token_" + token;
String redisToken = redisService.get(tokenKey);
//如果redis里面没有token,说明该token失效
if (ObjectUtils.isEmpty(redisToken)) {
throw new CustomerAuthenticationException("token已过期");
}
//如果token和Redis中的token不一致,则验证失败
if (!token.equals(redisToken)) {
throw new CustomerAuthenticationException("token验证失败");
}
//如果存在token,则从token中解析出用户名
String username = jwtUtils.getUsernameFromToken(token);
//如果用户名为空,则解析失败
if (ObjectUtils.isEmpty(username)) {
throw new CustomerAuthenticationException("token解析失败");
}
//获取用户信息
UserDetails userDetails =
customerUserDetailsService.loadUserByUsername(username);
//判断用户信息是否为空
if (userDetails == null) {
throw new CustomerAuthenticationException("token验证失败");
}
//创建身份验证对象
UsernamePasswordAuthenticationToken authenticationToken = new
UsernamePasswordAuthenticationToken(userDetails, null,
userDetails.getAuthorities());
authenticationToken.setDetails(new
WebAuthenticationDetailsSource().buildDetails(request));
//设置到Spring Security上下文
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}

3.6.6 配置token验证过滤器类

将CheckTokenFilter过滤器类交给Spring Security进行管理,在SpringSecurityConfig配置类中添加如下代 码。

@Resource
private CheckTokenFilter checkTokenFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
//登录前进行过滤
http.addFilterBefore(checkTokenFilter,
UsernamePasswordAuthenticationFilter.class);
http.formLogin()
//省略后续代码....
}

3.6.7 编写自定义异常类

package com.manong.config.security.exception;
import org.springframework.security.core.AuthenticationException;
/**
* 自定义验证异常类
*/
public class CustomerAuthenticationException extends AuthenticationException {
public CustomerAuthenticationException(String message){
super(message);
}
}

3.6.8 token验证失败处理 在LoginFailureHandler用户认证失败处理类中加入判断。

/**
* 用户认证失败处理类
*/
@Component
public class LoginFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException exception) throws
IOException, ServletException {
//设置客户端响应编码格式
response.setContentType("application/json;charset=UTF-8");
//获取输出流
ServletOutputStream outputStream = response.getOutputStream();
String message = null;//提示信息
int code = 500;//错误编码
//判断异常类型
if(exception instanceof AccountExpiredException){
message = "账户过期,登录失败!";
}else if(exception instanceof BadCredentialsException){
message = "用户名或密码错误,登录失败!";
}else if(exception instanceof CredentialsExpiredException){
message = "密码过期,登录失败!";
}else if(exception instanceof DisabledException){
message = "账户被禁用,登录失败!";
}else if(exception instanceof LockedException){
message = "账户被锁,登录失败!";
}else if(exception instanceof InternalAuthenticationServiceException){
message = "账户不存在,登录失败!";
}else if(exception instanceof CustomerAuthenticationException){
message = exception.getMessage();
code = 600;
}else{
message = "登录失败!";
}
//将错误信息转换成JSON
String result =
JSON.toJSONString(Result.error().code(code).message(message));
outputStream.write(result.getBytes(StandardCharsets.UTF_8));
outputStream.flush();
outputStream.close();
}
}

3.6.9 认证成功处理 修改LoginSuccessHandler登录认证成功处理类,将token保存到Redis缓存中。

@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
@Resource
private JwtUtils jwtUtils;
@Resource
private RedisService redisService;
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication) throws IOException,
ServletException {
//省略原有代码......
//把生成的token存到redis
String tokenKey = "token_"+token;
redisService.set(tokenKey,token,jwtUtils.getExpiration() / 1000);
}
}

3.7 刷新token信息 3.7.1 创建TokenVo类 在com.manong.vo包下创建TokenVo类,该类用于保存Token信息。

package com.manong.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TokenVo {
//过期时间
private Long expireTime;
//token
private String token;
}

3.7.2 编写刷新token方法 在com.manong.controller包下创建SysUserController控制器类,并在该类中编写refreshToken刷新token的 方法。

@RestController
@RequestMapping("/api/sysUser")
public class SysUserController {
@Resource
private RedisService redisService;
@Resource
private JwtUtils jwtUtils;
/**
* 刷新token
*
* @param request
* @return
*/
@PostMapping("/refreshToken")
public Result refreshToken(HttpServletRequest request) {
//从header中获取前端提交的token
String token = request.getHeader("token");
//如果header中没有token,则从参数中获取
if (ObjectUtils.isEmpty(token)) {
token = request.getParameter("token");
}
//从Spring Security上下文获取用户信息
Authentication authentication =
SecurityContextHolder.getContext().getAuthentication();
//获取身份信息
UserDetails details = (UserDetails) authentication.getPrincipal();
//重新生成token
String reToken = "";
//验证原来的token是否合法
if (jwtUtils.validateToken(token, details)) {
//生成新的token
reToken = jwtUtils.refreshToken(token);
}
//获取本次token的到期时间,交给前端做判断
long expireTime = Jwts.parser().setSigningKey(jwtUtils.getSecret())
.parseClaimsJws(reToken.replace("jwt_", ""))
.getBody().getExpiration().getTime();
//清除原来的token信息
String oldTokenKey = "token_" + token;
redisService.del(oldTokenKey);
//存储新的token
String newTokenKey = "token_" + reToken;
redisService.set(newTokenKey, reToken, jwtUtils.getExpiration() / 1000);
//创建TokenVo对象
TokenVo tokenVo = new TokenVo(expireTime, reToken);
//返回数据
return Result.ok(tokenVo).message("token生成成功");
}
}

3.7.3 接口运行测试 1. 先运行用户登录请求,生成token信息 2. 测试查询全部用户信息,预期结果是查询成功 3. 运行刷新token接口,重新生成token信息

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-09-13 10:59:35  更:2022-09-13 11:04:54 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 13:22:40-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码