目录
登录接口说明:LoginController
一、获取图形验证码接口
controller层:
service层:
二、登录接口
controller层:
service层:
mapper层:
三、JwtUtil创建
四、获取登陆用户个人信息接口
controller层:
(重要)因为我们解析token的方法是在拦截器里调用,所以如果我们每一次获取登录者的时候,要获取token进行解析,连续调用两次parseToken()方法就不太合理了。所以为避免重复解析,通常会在拦截器将Token解析完毕后,将结果保存至ThreadLocal中,这样一来,我们便可以在整个请求的处理流程中进行访问了。
修改拦截器:
service层:
登录接口说明:LoginController
@Tag(name = "后台管理系统登录管理")
@RestController
@RequestMapping("/admin")
public class LoginController {
@Autowired
private LoginService service;
}
一、获取图形验证码接口
接口名称:getCaptcha
请求方式:Get
请求路径:/admin/login/captcha
请求参数:无
返回类型:CaptchaVo
我们先查看CaptchaVo有哪些属性:
@Data
@Schema(description = "图像验证码")
@AllArgsConstructor
public class CaptchaVo {
@Schema(description="验证码图片信息")
private String image;
@Schema(description="验证码key")
private String key;
}
需要获取验证码图片的信息和存储在redis里的key。那么怎么生成验证码呢?我们需要使用开源的验证码生成工具EasyCaptcha。
导入maven依赖:
<dependency>
<groupId>***.github.whvcse</groupId>
<artifactId>easy-captcha</artifactId>
</dependency>
controller层:
@Operation(summary = "获取图形验证码")
@GetMapping("login/captcha")
public Result<CaptchaVo> getCaptcha() {
CaptchaVo result= service.getCaptcha();
return Result.ok(result);
}
service层:
public CaptchaVo getCaptcha() {
Spe***aptcha spe***aptcha = new Spe***aptcha(130, 48, 4); //分别设置验证码区域的长、宽、以及验证码长度。
String code = spe***aptcha.text().toLowerCase(); //把验证码转成小写
String key = RedisConstant.ADMIN_LOGIN_PREFIX + UUID.randomUUID(); //遵循命名规范
stringRedisTemplate.opsForValue().set(key,code,RedisConstant.ADMIN_LOGIN_CAPTCHA_TTL_SEC, TimeUnit.SECONDS); //把key和验证码值存入redis
return new CaptchaVo(spe***aptcha.toBase64(),key);
}
本项目Reids中的key需遵循以下命名规范:项目名:功能模块名:其他,例如
admin:login:123456
二、登录接口
接口名称:login
请求方式:Post
请求路径:/admin/login
请求参数:@RequestBody LoginVo loginVo
返回类型:Result<String>
controller层:
@Operation(summary = "登录")
@PostMapping("login")
public Result<String> login(@RequestBody LoginVo loginVo) {
String jwt=service.login(loginVo);
return Result.ok(jwt);
}
loginVo用来封装前端登录界面用户提交的用户名、密码、验证码以及验证码在redis的key。
@Data
@Schema(description = "后台管理系统登录信息")
public class LoginVo {
@Schema(description="用户名")
private String username;
@Schema(description="密码")
private String password;
@Schema(description="验证码key")
private String captchaKey;
@Schema(description="验证码code")
private String captchaCode;
}
service层:
public String login(LoginVo loginVo) {
if (loginVo.getCaptchaCode()==null){ //判断输入的验证码是否为空
throw new LeaseException(ResultCodeEnum.ADMIN_CAPTCHA_CODE_NOT_FOUND);
}
String code = stringRedisTemplate.opsForValue().get(loginVo.getCaptchaKey()); //从redis中获取code
if (code==null){ //判断查询的code是否为空
throw new LeaseException(ResultCodeEnum.ADMIN_CAPTCHA_CODE_EXPIRED);
}
if (!code.equals(loginVo.getCaptchaCode().toLowerCase())){ //如果code与输入的code的小写相等
throw new LeaseException(ResultCodeEnum.ADMIN_CAPTCHA_CODE_ERROR);
}
SystemUser systemUser = systemUserMapper.selectOneByUsername(loginVo.getUsername()); //通过用户名查询用户
if (systemUser==null){
throw new LeaseException(ResultCodeEnum.ADMIN_A***OUNT_NOT_EXIST_ERROR);
}
if (systemUser.getStatus()== BaseStatus.DISABLE){
throw new LeaseException(ResultCodeEnum.ADMIN_A***OUNT_DISABLED_ERROR);
}
if (!systemUser.getPassword().equals(DigestUtils.md5Hex(loginVo.getPassword()))){
throw new LeaseException(ResultCodeEnum.ADMIN_A***OUNT_ERROR);
}
return JwtUtil.createToken(systemUser.getId(),systemUser.getUsername()); //返回token
}
mapper层:
自定义通过用户名字查询用户的方法。
<select id="selectOneByUsername" resultType="***.atguigu.lease.model.entity.SystemUser">
select id,
username,
password,
name,
type,
phone,
avatar_url,
additional_info,
post_id,
status
from system_user
where is_deleted = 0
and username = #{username}
</select>
三、JwtUtil创建
JwtUtil是用来生成token来识别登录的用户是否合法,如果合法则从数据库中查询用户信息,并响应给前端。而且后续前端想要请求后端接口也需要解析携带的token。
导入依赖:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<scope>runtime</scope>
</dependency>
public class JwtUtil {
private static long tokenExpiration = 60 * 60 * 1000L;
private static SecretKey tokenSignKey = Keys.hmacShaKeyFor("M0PKKI6pYGVWWfDZw90a0lTpGYX1d4AQ".getBytes());
/**
* 创建token方法
* @param userId
* @param username
* @return
*/
public static String createToken(Long userId, String username) {
String token = Jwts.builder().
setSubject("LOGIN_USER").
setExpiration(new Date(System.currentTimeMillis() + 3600000)).
claim("userId", userId).
claim("username", username).
signWith(tokenSignKey, SignatureAlgorithm.HS256).
***pact();
return token;
}
/**
* 解析token方法
* @param token
* @return
*/
public static Claims parseToken(String token){
if (token==null){
throw new LeaseException(ResultCodeEnum.ADMIN_LOGIN_AUTH);
}
try {
JwtParser jwtParser = Jwts.parser().setSigningKey(tokenSignKey)
.build();
Jws<Claims> claimsJws = jwtParser.parseClaimsJws(token);
Claims claims = claimsJws.getBody();
return claims;
}catch (ExpiredJwtException e){
throw new LeaseException(ResultCodeEnum.TOKEN_EXPIRED);
}catch (JwtException e){
throw new LeaseException(ResultCodeEnum.TOKEN_INVALID);
}
}
四、获取登陆用户个人信息接口
接口名称:info
请求方式:Get
请求路径:/admin/info
请求参数:无
返回类型:Result<SystemUserInfoVo>
controller层:
返回的类型为VO对象,需要自己定义查询方法。
(重要)因为我们解析token的方法是在拦截器里调用,所以如果我们每一次获取登录者的时候,要获取token进行解析,连续调用两次parseToken()方法就不太合理了。所以为避免重复解析,通常会在拦截器将Token解析完毕后,将结果保存至ThreadLocal中,这样一来,我们便可以在整个请求的处理流程中进行访问了。
ThreadLocal的主要作用是为每个使用它的线程提供一个独立的变量副本,使每个线程都可以操作自己的变量,而不会互相干扰,其用法如下图所示。
每个ThreadLocal对象都拥有set、get、remove方法,我们只需要编写逻辑调用即可自定义存储、获取、清理本地线程对象的方法。
public class LoginUserHolder {
public static ThreadLocal<LoginUser> threadLocal = new ThreadLocal<>(); //创建本地线程对象
public static void setLoginUser(LoginUser loginUser) {
threadLocal.set(loginUser); //把登录用户信息存储进本地线程对象
}
public static LoginUser getLoginUser() {
return threadLocal.get(); //获取登录用户信息
}
public static void clear() {
threadLocal.remove(); //清理存储的用户信息
}
}
@Operation(summary = "获取登录用户个人信息")
@GetMapping("info")
public Result<SystemUserInfoVo> info() {
Long userId = LoginUserHolder.getLoginUser().getUserId();
SystemUserInfoVo systemUserInfoVo=service.getLoginUserInfoById(userId);
return Result.ok(systemUserInfoVo);
}
修改拦截器:
@***ponent
public class AuthenticationIntercepter implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("a***ess-token");
Claims claims = JwtUtil.parseToken(token);
Long userId = claims.get("userId", Long.class);
String username = claims.get("username", String.class);
LoginUserHolder.setLoginUser(new LoginUser(userId, username)); //封装登录对象存储进本地线程对象
return true;
}
@Override
public void after***pletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
LoginUserHolder.clear(); //清理本地线程方法
}
}
service层:
public SystemUserInfoVo getLoginUserInfoById(Long userId) {
SystemUser systemUser = systemUserMapper.selectById(userId);
SystemUserInfoVo systemUserInfoVo = new SystemUserInfoVo();
systemUserInfoVo.setAvatarUrl(systemUser.getAvatarUrl());
systemUserInfoVo.setName(systemUser.getName());
return systemUserInfoVo;
}