1. pom依赖引入
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.15</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.6.2</version>
</dependency>
2. 检验参数
token:
tokenHeader: Authorization
tokenHead: Bearer
salt: T-zhigong
expirationTime: 10080
signer: T-zhigong
redis_token_key: HE_TOKEN
3. JWTToken工具类编写
package com.zhigong.heavenearth.utils;
import cn.hutool.core.util.IdUtil;
import io.jsonwebtoken.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.util.Date;
import java.util.HashMap;
@Component
public class JWTTokenUtil {
@Value("${token.salt}")
private String salt;
@Value("${token.signer}")
private String signer;
@Value("${token.expirationTime}")
private Long expiration;
public String creatToken(UserDetails userDetails) {
byte[] bytes = DatatypeConverter.parseBase64Binary(salt);
SecretKeySpec secretKeySpec = new SecretKeySpec(bytes, SignatureAlgorithm.HS256.getJcaName());
long currentTimeMillis = System.currentTimeMillis();
Date createDate = new Date(currentTimeMillis);
long expiredTimeMillis = currentTimeMillis + expiration * 60 * 1000;
Date expiredDate = new Date(expiredTimeMillis);
HashMap<String, Object> claims = new HashMap<>();
claims.put("username", userDetails.getUsername());
JwtBuilder jwtBuilder = Jwts.builder()
.setHeaderParam("typ", "JWT")
.setClaims(claims)
.setSubject(userDetails.getUsername())
.setId(IdUtil.simpleUUID())
.setIssuedAt(createDate)
.setIssuer(signer)
.setAudience("IE")
.setExpiration(expiredDate)
.signWith(SignatureAlgorithm.HS256, secretKeySpec);
return jwtBuilder.compact();
}
public Claims decryptToken(String token) {
byte[] bytes = DatatypeConverter.parseBase64Binary(salt);
SecretKeySpec secretKeySpec = new SecretKeySpec(bytes, SignatureAlgorithm.HS256.getJcaName());
Claims claims;
try {
claims = Jwts.parser()
.setSigningKey(secretKeySpec)
.parseClaimsJws(token)
.getBody();
} catch (ExpiredJwtException e) {
claims = e.getClaims();
}
return claims;
}
}
4. Security配置类编写
package com.zhigong.heavenearth.config;
import com.zhigong.heavenearth.config.security.SecurityTokenAuthenticationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import java.util.ArrayList;
import java.util.List;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private SecurityTokenAuthenticationFilter securityTokenAuthenticationFilter;
@Value("${spring.profiles.active}")
private String env;
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers(getUrls()).permitAll()
.anyRequest().authenticated();
httpSecurity.addFilterBefore(securityTokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
httpSecurity.headers().cacheControl();
httpSecurity.headers().frameOptions().disable();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
private String[] getUrls() {
List<String> list = new ArrayList<>();
if (!env.contains("prod")) {
list.add("/swagger-ui.html/**");
list.add("/swagger-ui/**");
list.add("/swagger-resources/**");
list.add("/webjars/**");
list.add("/v3/**");
list.add("/v2/**");
list.add("/*/api-docs");
list.add("/doc.html");
list.add("/**");
}
list.add("/userAccount/login");
String[] strings = list.toArray(new String[0]);
return strings;
}
}
5. Security用户登录详情处理
package com.zhigong.heavenearth.config.security;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.zhigong.heavenearth.mapper.UserAccountMapper;
import com.zhigong.heavenearth.dto.user.LoginDetailsDTO;
import com.zhigong.heavenearth.dto.user.UserAccountDTO;
import com.zhigong.heavenearth.pojo.UserAccount;
import com.zhigong.heavenearth.utils.JWTTokenUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class SecurityUserDetailService implements UserDetailsService {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private UserAccountMapper userAccountMapper;
@Autowired
private JWTTokenUtil jwtTokenUtil;
@Value("${token.redis_token_key}")
private String redisTokenKey;
@Value("${token.expirationTime}")
private int expirationTime;
@Override
public LoginDetailsDTO loadUserByUsername(String username) throws UsernameNotFoundException {
LambdaQueryWrapper<UserAccount> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(UserAccount::getAccount, username);
UserAccount userAccount = userAccountMapper.selectOne(queryWrapper);
if (ObjectUtil.isEmpty(userAccount)) {
throw new UsernameNotFoundException("用户不存在");
}
LoginDetailsDTO loginDetailsDTO = new LoginDetailsDTO(userAccount.getAccount(), userAccount.getPassword());
String token = jwtTokenUtil.creatToken(loginDetailsDTO);
UserAccountDTO userAccountDTO = new UserAccountDTO();
BeanUtil.copyProperties(userAccount, userAccountDTO);
loginDetailsDTO.setUserAccountDTO(userAccountDTO);
loginDetailsDTO.setToken(token);
ValueOperations<String, LoginDetailsDTO> valueOperations = redisTemplate.opsForValue();
valueOperations.set(redisTokenKey + ":" + token, loginDetailsDTO, expirationTime, TimeUnit.MINUTES);
return loginDetailsDTO;
}
}
6. Security Token拦截器
package com.zhigong.heavenearth.config.security;
import com.zhigong.heavenearth.dto.user.LoginDetailsDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
@Slf4j
public class SecurityTokenAuthenticationFilter extends OncePerRequestFilter {
@Autowired
RedisTemplate redisTemplate;
@Value("${token.tokenHeader}")
private String tokenHeader;
@Value("${token.tokenHead}")
private String tokenHead;
@Value("${token.redis_token_key}")
private String redisTokenKey;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
final String authHead = request.getHeader(this.tokenHeader);
if (authHead != null && authHead.startsWith(tokenHead)) {
final String authToken = authHead.substring(tokenHead.length());
if (redisTemplate.hasKey(redisTokenKey + ":" + authToken)) {
ValueOperations<String, LoginDetailsDTO> valueOperations = redisTemplate.opsForValue();
LoginDetailsDTO loginDetailsDTO = valueOperations.get(redisTokenKey + ":" + authToken);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginDetailsDTO, null, loginDetailsDTO.getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}
filterChain.doFilter(request, response);
}
}
7. 改写登录服务逻辑
@ApiOperation("登录")
@PostMapping("login")
@LogAnnotation(OperateModule = "登录", OperateType = "登录", OperateDesc = "系统登录")
public Result login(@RequestBody LoginParamDTO loginParamDTO) {
LoginDetailsDTO loginDetailsDTO = securityUserDetailService.loadUserByUsername(loginParamDTO.getUserName());
return Result.success(loginDetailsDTO);
}
8. 请求头预览效果(以Swagger为例)
