jwt用户登录实践 v1.0
思路
- 用户请求登录,携带账号密码,验证码
- 数据库查询是否该用户名下有账号信息
- 有则返回生成jwtUser,没有则抛出用户不存在的异常
- 在创建jwtUser信息时,实时查找出角色和权限数据
- 根据查询的用户信息,判断密码是否正确,并且判断是否可用。
- 如果账号正常可用,生成jwt令牌
- 返回令牌和账户
- 登录时校验 令牌
代码
pojo对象
用户登录时的需要携带的参数,作为一个用户权限对象AuthorizationUser
。主要携带参数登录账号(手机号或邮箱或账号)和登录密码。
@Data
public class AuthorizationUser {
@NotBlank
private String username;
@NotBlank
private String password;
@Override
public String toString() {
return "{username=" + username + ", password= ******}";
}
}
用户具体信息的对象,jwtUser
@Getter
@AllArgsConstructor
public class JwtUser implements UserDetails {
@JsonIgnore
private final Long id;
private final String username;
@JsonIgnore
private final String password;
private final String avatar;
private final String email;
private final String phone;
private final String dept;
private final String job;
@JsonIgnore
private final Collection<GrantedAuthority> authorities;
private final boolean enabled;
private Timestamp createTime;
@JsonIgnore
private final Date lastPasswordResetDate;
@JsonIgnore
@Override
public boolean isAccountNonExpired() {
return true;
}
@JsonIgnore
@Override
public boolean isAccountNonLocked() {
return true;
}
@JsonIgnore
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@JsonIgnore
@Override
public String getPassword() {
return password;
}
@Override
public boolean isEnabled() {
return enabled;
}
public Collection getRoles() {
return authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toSet());
}
}
用户信息返回给前段时,需要带有token令牌和用户信息,权限信息对象AuthenticationInfo
@Getter
@AllArgsConstructor
public class AuthenticationInfo implements Serializable {
private final String token;
private final JwtUser user;
}
controller
@Slf4j
@RestController
@RequestMapping("auth")
public class AuthenticationController {
@Value("${jwt.header}")
private String tokenHeader;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
@Qualifier("jwtUserDetailsService")
private UserDetailsService userDetailsService;
/**
* 登录授权
* @param authorizationUser
* @return
*/
@Log("用户登录")
@PostMapping(value = "${jwt.auth.path}")
public ResponseEntity login(@Validated @RequestBody AuthorizationUser authorizationUser){
final JwtUser jwtUser = (JwtUser) userDetailsService.loadUserByUsername(authorizationUser.getUsername());
if(!jwtUser.getPassword().equals(EncryptUtils.encryptPassword(authorizationUser.getPassword()))){
throw new AccountExpiredException("密码错误");
}
if(!jwtUser.isEnabled()){
throw new AccountExpiredException("账号已停用,请联系管理员");
}
// 生成令牌
final String token = jwtTokenUtil.generateToken(jwtUser);
// 返回 token
return ResponseEntity.ok(new AuthenticationInfo(token,jwtUser));
}
/**
* 获取用户信息
* @return
*/
@GetMapping(value = "${jwt.auth.account}")
public ResponseEntity getUserInfo(){
UserDetails userDetails = SecurityContextHolder.getUserDetails();
JwtUser jwtUser = (JwtUser)userDetailsService.loadUserByUsername(userDetails.getUsername());
return ResponseEntity.ok(jwtUser);
}
}
service
@Service
@CacheConfig(cacheNames = "role")
public class JwtPermissionService {
@Autowired
private RoleRepository roleRepository;
@Autowired
private PermissionRepository permissionRepository;
@Cacheable(key = "'loadPermissionByUser:' + #p0.username")
public Collection<GrantedAuthority> mapToGrantedAuthorities(User user) {
System.out.println("--------------------loadPermissionByUser:" + user.getUsername() + "---------------------");
Set<Role> roles = roleRepository.findByUsers_Id(user.getId());
Set<Permission> permissions = new HashSet<>();
for (Role role : roles) {
Set<Role> roleSet = new HashSet<>();
roleSet.add(role);
permissions.addAll(permissionRepository.findByRoles_Id(role.getId()));
}
return permissions.stream()
.map(permission -> new SimpleGrantedAuthority(permission.getName()))
.collect(Collectors.toList());
}
}
@Service
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
public class JwtUserDetailsService implements UserDetailsService {
@Autowired
private UserService userService;
@Autowired
private JwtPermissionService permissionService;
@Override
public UserDetails loadUserByUsername(String username){
User user = userService.findByName(username);
if (user == null) {
throw new EntityNotFoundException(User.class, "name", username);
} else {
return createJwtUser(user);
}
}
public UserDetails createJwtUser(User user) {
return new JwtUser(
user.getId(),
user.getUsername(),
user.getPassword(),
user.getAvatar(),
user.getEmail(),
user.getPhone(),
user.getDept().getName(),
user.getJob().getName(),
permissionService.mapToGrantedAuthorities(user),
user.getEnabled(),
user.getCreateTime(),
user.getLastPasswordResetTime()
);
}
}
工具类
@Component
public class JwtTokenUtil implements Serializable {
private static final long serialVersionUID = -3301605591108950415L;
private Clock clock = DefaultClock.INSTANCE;
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
@Value("${jwt.header}")
private String tokenHeader;
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
public Date getIssuedAtDateFromToken(String token) {
return getClaimFromToken(token, Claims::getIssuedAt);
}
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(clock.now());
}
private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) {
return (lastPasswordReset != null && created.before(lastPasswordReset));
}
private Boolean ignoreTokenExpiration(String token) {
// here you specify tokens, for that the expiration is ignored
return false;
}
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return doGenerateToken(claims, userDetails.getUsername());
}
private String doGenerateToken(Map<String, Object> claims, String subject) {
final Date createdDate = clock.now();
final Date expirationDate = calculateExpirationDate(createdDate);
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(createdDate)
.setExpiration(expirationDate)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
public Boolean canTokenBeRefreshed(String token, Date lastPasswordReset) {
final Date created = getIssuedAtDateFromToken(token);
return !isCreatedBeforeLastPasswordReset(created, lastPasswordReset)
&& (!isTokenExpired(token) || ignoreTokenExpiration(token));
}
public String refreshToken(String token) {
final Date createdDate = clock.now();
final Date expirationDate = calculateExpirationDate(createdDate);
final Claims claims = getAllClaimsFromToken(token);
claims.setIssuedAt(createdDate);
claims.setExpiration(expirationDate);
return Jwts.builder()
.setClaims(claims)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
public Boolean validateToken(String token, UserDetails userDetails) {
JwtUser user = (JwtUser) userDetails;
final String username = getUsernameFromToken(token);
final Date created = getIssuedAtDateFromToken(token);
// final Date expiration = getExpirationDateFromToken(token);
// 如果token存在,且token创建日期 > 最后修改密码的日期 则代表token有效
return (
username.equals(user.getUsername())
&& !isTokenExpired(token)
&& !isCreatedBeforeLastPasswordReset(created, user.getLastPasswordResetDate())
);
}
private Date calculateExpirationDate(Date createdDate) {
return new Date(createdDate.getTime() + expiration);
}
}