后台管理系统的通用权限解决方案(十四)基于JWT实现登录功能
基于JWT实现登录功能
说明:以下代码仅仅是主体代码,其中涉及的实体类、码表、工具类等没有贴出来,完整代码可以在本篇文章中直接下载。
- 1)在
LoginController
中创建doLogin
方法
// com.itweid.auth.controller.LoginController@PostMapping("/doLogin")
@ApiOperation("用户登录")
public BaseResult<LoginVO> doLogin(@Validated @RequestBody LoginQuery loginQuery) {return loginService.doLogin(loginQuery);
}
- 2)创建
ILoginService
接口及LoginServiceImpl
实现类,在实现类在实现doLogin
方法
package com.itweid.auth.service.impl;import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.extension.toolkit.Db;
import com.itweid.auth.mapper.AuthResourceMapper;
import com.itweid.auth.service.ILoginService;
import com.itweid.common.code.ErrorCode;
import com.itweid.common.code.RedisCode;
import com.itweid.common.entity.AuthResource;
import com.itweid.common.entity.AuthUser;
import com.itweid.common.pojo.*;
import com.itweid.jwt.pojo.JwtUserInfo;
import com.itweid.jwt.utils.AuthTokenUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
public class LoginServiceImpl implements ILoginService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Autowiredprivate AuthTokenUtils authTokenUtils;@Autowiredprivate AuthResourceMapper authResourceMapper;@Overridepublic BaseResult<LoginVO> doLogin(LoginQuery loginQuery) {log.info(loginQuery.toString());// 校验验证码是否正确BaseResult checkCodeRes = checkCode(loginQuery.getKey(), loginQuery.getCode());if(!checkCodeRes.isSuccess()) {return BaseResult.setError(checkCodeRes);}// 校验账号、密码是否正确BaseResult<AuthUser> checkPassRes = checkPass(loginQuery.getAccount(), loginQuery.getPassword());if(!checkPassRes.isSuccess()) {return BaseResult.setError(checkPassRes);}// 为用户生成jwt令牌AuthUser authUser = checkPassRes.getData();Token token = generateUserToken(authUser);// 查询当前用户可以访问的资源权限List<AuthResource> authResourceList = authResourceMapper.findVisibleResourceByUserId(authUser.getId());List<String> permissionList = null;if(authResourceList != null && !authResourceList.isEmpty()) {// 用户对应的权限(给前端使用的)permissionList = authResourceList.stream().map(AuthResource::getCode).collect(Collectors.toList());// 将用户对应的权限(给后端网关使用的)进行缓存List<String> visibleResource = authResourceList.stream().map((resource -> {return resource.getMethod() + resource.getUrl();})).collect(Collectors.toList());stringRedisTemplate.opsForHash().put(RedisCode.USER_RESOURCE, authUser.getId().toString(), visibleResource.toString());}// 封装返回结果UserVO userVO = new UserVO();BeanUtil.copyProperties(authUser, userVO);LoginVO loginDTO = LoginVO.builder().user(userVO).token(token).permissionsList(permissionList).build();return BaseResult.setOk(loginDTO);}private BaseResult checkCode(String key, String code) {// 从缓存中获取验证码并进行验证Object captchaObj = stringRedisTemplate.opsForHash().get(RedisCode.CAPTCHA, key);log.info("Redis => get {} {} => {}", RedisCode.CAPTCHA, key, captchaObj);if (captchaObj == null) {return BaseResult.setError(ErrorCode.VALID_CODE_IS_OVERDUE);}if (!StringUtils.equalsIgnoreCase(code, String.valueOf(captchaObj))) {return BaseResult.setError(ErrorCode.VALID_CODE_IS_WRONG);}// 验证通过,立即从缓存中删除验证码Long delete = stringRedisTemplate.opsForHash().delete(RedisCode.CAPTCHA, key);log.info("Redis => del {} {} => {}", RedisCode.CAPTCHA, key, delete);return BaseResult.setOk();}public BaseResult<AuthUser> checkPass(String account, String password) {AuthUser authUser = Db.lambdaQuery(AuthUser.class).eq(AuthUser::getAccount, account).one();// 将前端提交的密码进行md5加密String md5Hex = DigestUtils.md5Hex(password);if (authUser == null || !authUser.getPassword().equals(md5Hex)) {// 认证失败return BaseResult.setError(ErrorCode.ACCOUNT_PASS_IS_WRONG);}// 认证成功return BaseResult.setOk(authUser);}private Token generateUserToken(AuthUser authUser){JwtUserInfo jwtUserInfo = new JwtUserInfo(authUser.getId(), authUser.getAccount());return authTokenUtils.generateUserToken(jwtUserInfo, null);}
}
由以上代码可知,实现登录的逻辑主要有以下几步:
-
校验验证码是否正确:从缓存中获取已经保存好的验证码并进行验证,验证失败则返回登录失败,验证通过则立即从缓存中删除验证码。
-
校验账号、密码是否正确:根据账号从数据库查询对应的用户信息,并进行密码验证,验证失败则返回登录失败。
-
为用户生成jwt令牌:根据用户ID、用户账号生产JWT令牌,具体的生成逻辑可以参考:后台管理系统的通用权限解决方案(九)SpringBoot整合jjwt实现登录认证鉴权
-
查询当前用户可以访问的资源权限:根据用户ID查询出该用户可以访问的资源权限,并保存到缓存中,同时返回前端。
-
3)在
AuthResourceMapper
中创建findVisibleResourceByUserId
方法,并在对应的AuthResourceMapper.xml
中写SQL语句
@Mapper
public interface AuthResourceMapper extends BaseMapper<AuthResource> {List<AuthResource> findVisibleResourceByUserId(Long userId);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itweid.auth.mapper.AuthResourceMapper"><!-- 通用查询结果列 --><sql id="Base_Column_List">id, create_user, create_time, update_user, update_time,code, name, menu_id, describe_, method, url</sql><select id="findVisibleResourceByUserId" resultType="AuthResource">select <include refid="Base_Column_List"/>from t_auth_resource<where>id in (select authority_id FROM t_auth_role_authority rainner join t_auth_user_role ur on ra.role_id = ur.role_idinner join t_auth_role r on r.id = ra.role_idwhere ur.user_id = #{userId, jdbcType=BIGINT} and r.`status` = trueand ra.authority_type = 'RESOURCE')</where></select>
</mapper>
- 4)启动项目,访问接口文档
http://127.0.0.1:8081/doc.html
- 5)在接口文档中调试登录功能
可见,前端已经获取到了用户信息、token信息、以及可使用的权限信息,后续前端向后端发起任何请求时都需要携带token,还可以使用权限信息控制页面的显示。
…
本节完,更多内容查阅:后台管理系统的通用权限解决方案