shiro前后端分离实战-实现用户登录认证访问授权
- 登录接口业务
-
- 密码工具
- 用户详情业务
- RESTful 控制层接口
- 配置登录接口白名单
- 接口业务分析
-
- 登录接口
- 获取用户详情接口
登录接口业务
@Override
public LoginResVO login(LoginReqVO reqVO) {
SysUser sysUser = sysUserMapper.getByUserName(reqVO.getUsername());
if (null == sysUser) {
throw new BusinessException(40001004, "用户不存在,请注册");
}
if (sysUser.getStatus() == 2) {
throw new BusinessException(40001005, "该用户已禁用,请联系系统管理员");
}
if (!PasswordUtils.matches(sysUser.getSalt(), reqVO.getPassword(), sysUser.getPassword())) {
throw new BusinessException(40001006, "密码不匹配,请重新登录");
}
LoginResVO loginResVO = new LoginResVO();
loginResVO.setUserId(sysUser.getId());
String token = UUID.randomUUID().toString();
loginResVO.setToken(token);
redis.set(token, sysUser.getId(), 60, TimeUnit.SECONDS);
return loginResVO;
}
密码工具
public class PasswordEncoder {
private final static String[] hexDigits = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d",
"e", "f"};
private final static String MD5 = "MD5";
private final static String SHA = "SHA";
private Object salt;
private String algorithm;
public PasswordEncoder(Object salt) {
this(salt, MD5);
}
public PasswordEncoder(Object salt, String algorithm) {
this.salt = salt;
this.algorithm = algorithm;
}
/**
* 密码加密
*
* @param rawPass
* @return
*/
public String encode(String rawPass) {
String result = null;
try {
MessageDigest md = MessageDigest.getInstance(algorithm);
// 加密后的字符串
result = byteArrayToHexString(md.digest(mergePasswordAndSalt(rawPass).getBytes("utf-8")));
} catch (Exception ex) {
}
return result;
}
/**
* 密码匹配验证
*
* @param encPass 密文
* @param rawPass 明文
* @return
*/
public boolean matches(String encPass, String rawPass) {
String pass1 = "" + encPass;
String pass2 = encode(rawPass);
return pass1.equals(pass2);
}
private String mergePasswordAndSalt(String password) {
if (password == null) {
password = "";
}
if ((salt == null) || "".equals(salt)) {
return password;
} else {
return password + "{" + salt.toString() + "}";
}
}
/**
* 转换字节数组为16进制字串
*
* @param b 字节数组
* @return 16进制字串
*/
private String byteArrayToHexString(byte[] b) {
StringBuffer resultSb = new StringBuffer();
for (int i = 0; i < b.length; i++) {
resultSb.append(byteToHexString(b[i]));
}
return resultSb.toString();
}
/**
* 将字节转换为16进制
*
* @param b
* @return
*/
private static String byteToHexString(byte b) {
int n = b;
if (n < 0)
n = 256 + n;
int d1 = n / 16;
int d2 = n % 16;
return hexDigits[d1] + hexDigits[d2];
}
public static void main(String[] args) {
}
}```
```java
public class PasswordUtils {
/**
* 匹配密码
*
* @param salt 盐
* @param rawPass 明文
* @param encPass 密文
* @return
*/
public static boolean matches(String salt, String rawPass, String encPass) {
return new PasswordEncoder(salt).matches(encPass, rawPass);
}
/**
* 明文密码加密
*
* @param rawPass 明文
* @param salt
* @return
*/
public static String encode(String rawPass, String salt) {
return new PasswordEncoder(salt).encode(rawPass);
}
/**
* 获取加密盐
*
* @return
*/
public static String getSalt() {
return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 20);
}
}
用户详情业务
@Override
public SysUser detail(String id) {
return sysUserMapper.selectByPrimaryKey(id);
}
RESTful 控制层接口
@RestController
@RequestMapping("/api")
@Api(tags = "用户模块", description = "用户模块相关接口")
public class UserController {
@Autowired
private IUserService userService;
@ApiOperation(value = "用户登录接口")
@PostMapping("/user/login")
public Map<String, Object> login(@RequestBody LoginReqVO vo) {
Map<String, Object> map = new HashMap<>();
map.put("code", 0);
map.put("data", userService.login(vo));
return map;
}
@ApiOperation(value = "根据用户ID查询人员信息")
@GetMapping("/user/{id}")
@RequiresPermissions("sys:user:detail")
public Map<String, Object> detail(@PathVariable("id") String id) {
Map<String, Object> map = new HashMap<>();
map.put("code", 0);
map.put("data", userService.detail(id));
return map;
}
}
配置登录接口白名单
ShiroConfig
中
shiroFilterFactoryBean
加入shiro 忽略拦截
接口业务分析
登录接口
- 用 LoginReqVO 接收用户提交过来的用户名密码的数据
- 把 vo 传入业务层接口进行业务处理
-
登录业务处理
a. 通过用户名去db查询用户信息
b. 判断是否查询到用信息
c. 判断是否被禁用
d. 校验密码是否正确
e. 生成token、把token做为key、userId作为value存入redis并设置过期时间为60分钟(后面认证需要)
f. 封装返回数据LoginRespVO 返回前端
获取用户详情接口
这个接口有两个关键点,因为用户首次登录后,后续再访问我们的系统资源的时候,无需再传入用户密码进行验证只需要携带登录生成的token可以了,我们的后端会围绕token使用shiro进行一系列的认证。当用户通过了用户认证的时候还需要进行授权,因为用户详解接口设置了访问权限(@RequiresPermissions(“sys:user:detail”))所以我们还要对访问的用户进行授权。