JWT
本文代码截取自实际项目。
jwt(Json Web Token),一个token,令牌。
简单流程:
用户登录成功后,后端返回一个token,也就是颁发给用户一个凭证。之后每一次访问,前端都需要携带这个token,后端通过token来解析出当前访问对象。
优点
1、一定程度上解放了后端,后端不需要再记录当前用户是谁,不需要再维护一个session,节省了开销。
2、session依赖于cookie,某些场合cookie是用不了的,比如用户浏览器cookie被禁用、移动端无法存储cookie等。
缺点
1、既然服务器通过token就可以知道当前用户是谁,说明其中包含了用户信息,有一定的泄露风险。
2、因为是无状态的,服务器不维持会话,那么每一次请求都会重新去数据库读取权限信息,性能有一定影响。
(如果想提高性能,可以将权限数据存到redis,但是如果用redis,就已经失去了jwt的优点,直接用普通的token+redis即可)
3、只能校验token是否正确,通过设定过期时间来确定其有效性,不可以手动注销。
先说怎么做,再说为什么。
代码
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<security.version>2.3.3.RELEASE</security.version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<jwt.version>0.9.1</jwt.version>
</dependency>
jwt工具类
public class JwtTokenUtils implements Serializable {
private static final long serialVersionUID = -3369436201465044824L;
//生成token
public static String createToken(String username) {
return Jwts.builder().setSubject(username)
.setExpiration(new Date(System.currentTimeMillis()+ Constants.Jwt.EXPIRATION))
.signWith(SignatureAlgorithm.HS512, Constants.Jwt.KEY).compressWith(CompressionCodecs.GZIP).compact();
}
//获取用户名
public static String getUserName(String token) {
return Jwts.parser().setSigningKey(Constants.Jwt.KEY).parseClaimsJws(token).getBody().getSubject();
}
}
涉及常量
public interface Constants {
public interface Jwt{
String KEY = "123123";
long EXPIRATION = 7200000;
String TOKEN_HEAD = "Authorization";
}
}
实体类和Service
为了减少对实体类的入侵,我又定义了一个对象
原实体类
@Data
@TableName("tb_user_info")
public class UserInfo implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.AUTO)
private Integer id;
private String loginName;
private String dispName;
private String password;
private Byte status;
@TableField(fill = FieldFill.INSERT)
private Integer createBy;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Integer updateBy;
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
@TableLogic
private Byte deleted;
}
定义对象
@Data
public class UserSecurity implements UserDetails {
private static final long serialVersionUID = -6777760550924505136L;
private UserInfo userInfo;
private List<String> permissionValues;
public UserSecurity(UserInfo userInfo) {
this.userInfo = userInfo;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new ArrayList<>();
for(String permissionValue : permissionValues) {
if(StringUtils.isEmpty(permissionValue)) continue;
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue);
authorities.add(authority);
}
return authorities;
}
@Override
public String getPassword() {
return userInfo.getPassword();
}
@Override
public String getUsername() {
return userInfo.getLoginName();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
service
@Service
public class UserInfoServiceImpl implements UserInfoService, UserDetailsService {
@Autowired
UserInfoMapper userInfoMapper;
@Autowired
PermissionService permissionService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("login_name", username);
UserInfo userInfo = userInfoMapper.selectOne(queryWrapper);
List<String> rights = permissionService.listPermissions(username);
UserSecurity userSecurity = new UserSecurity(userInfo);
userSecurity.setPermissionValues(rights);
return userSecurity;
}
}
配置类
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserInfoServiceImpl userInfoService;
@Autowired
JwtAuthorizationTokenFilter jwtAuthorizationTokenFilter;
@Autowired
TokenLoginFilter tokenLoginFilter;
@Autowired
JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userInfoService).passwordEncoder(passwordEncoderBean());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and().csrf().disable()
.authorizeRequests()
.antMatchers("/login").permitAll()
.antMatchers("/hello").permitAll()
.antMatchers("/swagger-ui.html").permitAll()
.antMatchers("/webjars
public UserInfo getCurrentUser() {
return (UserInfo) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}
}
这里的UserInfo对象,就是principal。
如果你上面放的不对象,而是用户名或者id,那么获取principal的时候,自然是对应的用户名或者id了。
种瓜得瓜,种豆得豆。
配置文件
截取核心片段加点注释。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程网。