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”))是以我們還要對通路的使用者進行授權。