使用Spring Security+jwt+redis实现登录注册逻辑
Spring Security
Spring Security 是一个提供身份验证、授权和防御常见攻击的框架。它为保护命令式和响应式应用程序提供了一流的支持,是保护基于 Spring 的应用程序的事实标准。
这篇博客主要是记录自己第一次使用springSecurity实现登录逻辑的过程。
使用Spring Security+jwt+redis实现登录注册逻辑
1. 导入依赖
<!--jwt依赖-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
</dependency>
<!--SpringSecurity启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
package com.h3m.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.util.Date;
@Data
@Component
@ConfigurationProperties(prefix = "config.jwt",ignoreInvalidFields = true)
public class JwtUtils {
private String secret;
private long expire;
// private String header;
/**
* 生成token, 根据用户名
* @param username
* @return
*/
public String createToken(String username){
// 获取当前时间
Date nowDate = new Date();
// 过期时间
Date expireDate = new Date(nowDate.getTime() + expire * 1000);
// 生成token
return Jwts.builder()
.setHeaderParam("typ", "JWT")
.setSubject(username)
.setIssuedAt(nowDate)
.setExpiration(expireDate)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
/**
* 获取token中的信息
* @param token
* @return
*/
public Claims getTokenClaim(String token){
try {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}catch (Exception e){
e.printStackTrace();
return null;
}
}
/**
* 获取token中的用户名
* @param token
* @return
*/
public String getUsernameFromToken(String token){
Claims claims = getTokenClaim(token);
if(claims == null){
return null;
}
return claims.getSubject();
}
/**
* Validate the token
* @param token
* @param userDetails
* @return 返回true表示有效
*/
public boolean validateToken(String token, UserDetails userDetails) {
String username = getUsernameFromToken(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
/**
* Check if the token is expired
* @param token
* @return 返回true表示过期
*/
private boolean isTokenExpired(String token) {
Date expiration = getTokenClaim(token).getExpiration();
return expiration.before(new Date());
}
}
package com.h3m.domain;
import com.h3m.constants.SYSConstant;
import lombok.Data;
@Data
public class Result {
private Integer code;
private String message;
private Object data;
public Result() {
}
public Result(Integer code) {
this.code = code;
}
public Result(Integer code, String message) {
this.code = code;
this.message = message;
}
public Result(Integer code, String message, Object data) {
this.code = code;
this.message = message;
this.data = data;
}
// 创建一些静态常量字段
public static final Result ADD_SUCCESS = new Result(SYSConstant.CODE_SUCCESS,SYSConstant.ADD_SUCCESS);
public static final Result ADD_ERROR = new Result(SYSConstant.CODE_ERROR, SYSConstant.ADD_ERROR);
public static final Result UPDATE_SUCCESS = new Result(SYSConstant.CODE_SUCCESS, SYSConstant.UPDATE_SUCCESS);
public static final Result UPDATE_ERROR = new Result(SYSConstant.CODE_ERROR, SYSConstant.UPDATE_ERROR);
public static final Result DELETE_SUCCESS = new Result(SYSConstant.CODE_SUCCESS, SYSConstant.DELETE_SUCCESS);
public static final Result DELETE_ERROR = new Result(SYSConstant.CODE_ERROR, SYSConstant.DELETE_ERROR);
public static final Result LOGIN_SUCCESS = new Result(SYSConstant.CODE_SUCCESS, SYSConstant.LOGIN_SUCCESS);
public static final Result LOGIN_ERROR = new Result(SYSConstant.CODE_ERROR, SYSConstant.LOGIN_ERROR);
}
我们需要创建一个用于Security的实体类,并实现UserDetails接口
package com.h3m.domain.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SecurityUser implements UserDetails {
private User CurrentUserInfo;
// private Collection<? extends GrantedAuthority> authorities;
private List<Access> accessList;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> authorities = accessList.stream()
.map(access -> new SimpleGrantedAuthority(access.getAccessName()))
.collect(Collectors.toList());
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;
}
}
这个工具类主要是用于返回数据到前端的。
package com.h3m.utils;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.h3m.domain.Result;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class ResponseUtils {
public static void writeResponse(HttpServletResponse response, Result result) throws IOException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=UTF-8");
ObjectMapper objectMapper = new ObjectMapper();
String jsonResponse = objectMapper.writeValueAsString(result);
response.getWriter().write(jsonResponse);
}
}
这里我们主要是重写三个方法,实现整个登录的过滤。
package com.h3m.filter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.h3m.constants.SYSConstant;
import com.h3m.domain.Result;
import com.h3m.utils.JwtUtils;
import com.h3m.domain.entity.SecurityUser;
import com.h3m.domain.entity.User;
import com.h3m.utils.ResponseUtils;
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 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 JwtLoginFilter extends UsernamePasswordAuthenticationFilter {
private JwtUtils jwtConfig;
private AuthenticationManager authenticationManager;
private RedisTemplate redisTemplate;
public JwtLoginFilter(AuthenticationManager authenticationManager, JwtUtils jwtConfig, RedisTemplate redisTemplate) {
this.authenticationManager = authenticationManager;
this.jwtConfig = jwtConfig;
this.redisTemplate = redisTemplate;
super.setFilterProcessesUrl("/login");
}
/**
* 这个方法是在用户登录的时候调用的方法
* @param request
* @param response
* @return
* @throws AuthenticationException
*/
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
//获取表单提供的数据
ObjectMapper objectMapper = new ObjectMapper();
try {
User user = objectMapper.readValue(request.getInputStream(), User.class);
//校验==认证的过程
Authentication authenticate = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword()
, new ArrayList<>())
);
return authenticate;
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("认证失败");
}
}
/**
* 这个方法是在用户登录成功后调用的方法
* @param request
* @param response
* @param chain
* @param authResult
*/
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws ServletException, IOException {
//得到用户名
SecurityUser securityUser = (SecurityUser) authResult.getPrincipal();
System.out.println("securityUser = " + securityUser);
String username = securityUser.getUsername();
//生成token
String token = jwtConfig.createToken(username);
//存入到redis username: 权限
redisTemplate.opsForValue().set(username, securityUser.getAuthorities());
// 返回token
response.addHeader("Authorization", "Bearer " + token);
// 登录成功后,封装用户以及对应权限信息返回
// 返回用户信息
Result result = new Result(SYSConstant.CODE_SUCCESS, SYSConstant.LOGIN_SUCCESS, securityUser);
ResponseUtils.writeResponse(response, result);
}
/**
* 这个方法是在用户登录失败后调用的方法
* @param request
* @param response
* @param failed
* @throws AuthenticationException
*/
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws AuthenticationException, ServletException, IOException {
ResponseUtils.writeResponse(response, new Result(SYSConstant.CODE_ERROR, "登录失败, 主要原因:"+failed.getMessage()));
}
}
package com.h3m.filter;
import com.h3m.utils.JwtUtils;
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.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
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 AuthFilter extends BasicAuthenticationFilter {
private JwtUtils jwtConfig;
private RedisTemplate redisTemplate;
public AuthFilter(AuthenticationManager authenticationManager, RedisTemplate redisTemplate, JwtUtils jwtConfig) {
super(authenticationManager);
this.jwtConfig = jwtConfig;
this.redisTemplate = redisTemplate;
}
/**
* 这里我们只需要重写过滤方法,在其中实现我们的逻辑即可
*
*/
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
// 从请求头中获取token所在的字段
String authorizationHeader = request.getHeader("Authorization");
if (authorizationHeader != null && !authorizationHeader.isEmpty() && authorizationHeader.startsWith("Bearer ")) {
// 获取token
String token = authorizationHeader.substring(7);
// 解析token,获得用户名
String username = jwtConfig.getUsernameFromToken(token);
//从redis中获得该用户名对应的权限
List<String> authList = (List<String>) redisTemplate.opsForValue().get(username);
//将取出的权限存入到权限上下文中,表示当前token对应的用户具备哪些权限
Collection<GrantedAuthority> authorities = new ArrayList<>();
if (authList != null) {
for (String auth : authList) {
authorities.add(new SimpleGrantedAuthority(auth));
}
}
// 生成认证信息对象
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, null, authorities);
// 把认证信息对象存入到权限上下文中
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
// 放行
chain.doFilter(request, response);
}
}
package com.h3m.handler;
import com.h3m.constants.SYSConstant;
import com.h3m.domain.Result;
import com.h3m.utils.JwtUtils;
import com.h3m.utils.ResponseUtils;
import com.mysql.cj.util.StringUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@RequiredArgsConstructor
@Component
public class TokenLogOutHandler implements LogoutHandler {
private final JwtUtils jwtConfig;
private final RedisTemplate redisTemplate;
@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
//1.获取token所在请求头
String authorizationHeader = request.getHeader("Authorization");
if (!StringUtils.isNullOrEmpty(authorizationHeader) && authorizationHeader.startsWith("Bearer ")) {
String token = authorizationHeader.substring(7); // 去掉 "Bearer " 前缀,得到 token
// 使用 token 进行后续操作
String username = jwtConfig.getUsernameFromToken(token);
redisTemplate.delete(username);
}
try {
ResponseUtils.writeResponse(response, new Result(SYSConstant.CODE_SUCCESS, "登出成功"));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
- 创建UserDetailServiceImpl实现类
package com.h3m.service.ServiceImpl;
import com.h3m.domain.entity.Access;
import com.h3m.domain.entity.SecurityUser;
import com.h3m.domain.entity.User;
import com.h3m.domain.entity.UserRole;
import com.h3m.service.AccessService;
import com.h3m.service.RoleAccessService;
import com.h3m.service.UserRoleService;
import com.h3m.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.PortResolverImpl;
import org.springframework.stereotype.Service;
import java.math.BigInteger;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@RequiredArgsConstructor
@Service
public class UserDetailServiceImpl implements UserDetailsService {
private final UserService userService;
private final UserRoleService userRoleService;
private final RoleAccessService roleAccessService;
private final AccessService accessService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//根据用户名从数据库查询到该用户的信息
User user = userService.selectByUsername(username);
if(Objects.isNull(user)) {
throw new UsernameNotFoundException("当前用户不存在");
}
// 查询用户的权限信息
// 1. 根据user_id查询用户的角色_id
UserRole userRole = userRoleService.selectByUserId(user.getId());
// 2. 根据角色id查询用户的权限_id
List<BigInteger> AccessIdList = roleAccessService.selectByRoleId(userRole.getRoleId());
// 3. 根据权限id查询用户的权限信息
List<Access> accessList = accessService.selectByAccessIdList(AccessIdList);
// 将权限信息转换为 GrantedAuthority 集合
// List<GrantedAuthority> authorities = accessList.stream()
// .map(access -> new SimpleGrantedAuthority(access.getAccessName()))
// .collect(Collectors.toList());
// 返回SecurityUser对象
return new SecurityUser(user, accessList);
}
}
- 创建SpringSecurity配置类
package com.h3m.config;
import com.h3m.filter.AuthFilter;
import com.h3m.filter.JwtLoginFilter;
import com.h3m.handler.TokenLogOutHandler;
import com.h3m.utils.JwtUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.servlet.http.HttpServletResponse;
@Configuration
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtUtils jwtConfig;
private final TokenLogOutHandler logoutHandler;
private final RedisTemplate redisTemplate;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http, AuthenticationManager authenticationManager) throws Exception {
JwtLoginFilter jwtLoginFilter = new JwtLoginFilter(authenticationManager, jwtConfig, redisTemplate);
AuthFilter authFilter = new AuthFilter(authenticationManager, redisTemplate, jwtConfig);
http
.csrf().disable()
.authorizeHttpRequests()
.antMatchers("/login", "/user/register").permitAll()
.anyRequest().authenticated()
.and()
.addFilter(jwtLoginFilter) // 登录请求需要经过这个过滤器
.addFilterBefore(authFilter, UsernamePasswordAuthenticationFilter.class) // 确保 authFilter 在合适的位置
.logout()
.logoutUrl("/logout")
.addLogoutHandler(logoutHandler)
.logoutSuccessHandler((request, response, authentication) -> response.setStatus(HttpServletResponse.SC_OK));
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
}
/login
由于我们在过滤器中已经实现了jwt的生成,以及返回信息,实际上登录接口这里我们不需要实现任何内容。
/**
* 登录接口
* @return
*/
@PostMapping("/login")
public void login() {
// 获取当前登录用户的信息
// Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
// SecurityUser currentUser = (SecurityUser) authentication.getPrincipal();
// return new Result(SYSConstant.CODE_SUCCESS, SYSConstant.LOGIN_SUCCESS, currentUser);
}
/register 我们在注册的时候需要对密码进行编码
private final PasswordEncoder passwordEncoder;
private final UserService userService;
private final UserRoleService userRoleService;
@PostMapping("/register")
public Result register(@RequestBody User user) {
log.info("用户注册: {}", user);
// 对密码进行加密
user.setPassword(passwordEncoder.encode(user.getPassword()));
// 注册用户, 调用save方法插入一条记录
boolean flag_1 = userService.save(user);
// 创建默认用户角色
UserRole userRole = new UserRole();
userRole.setUserId(user.getId());
// 插入一条记录, 虽然这里只传入了一个userId,但是roleId字段有默认值为2,即普通用户
boolean flag_2 = userRoleService.save(userRole);
if (flag_1 && flag_2) {
return new Result(SYSConstant.CODE_SUCCESS, "注册成功");
} else {
return new Result(SYSConstant.CODE_ERROR, "注册失败");
}
}
原文地址:https://blog.csdn.net/qq_45791526/article/details/142817324
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!