天天看點

Spring Boot 2.x + shiro前後端分離實戰-實作使用者登入認證通路授權 實戰篇(十二)登入接口業務使用者詳情業務RESTful 控制層接口配置登入接口白名單接口業務分析

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 忽略攔截

接口業務分析

登入接口

  1. 用 LoginReqVO 接收使用者送出過來的使用者名密碼的資料
  2. 把 vo 傳入業務層接口進行業務處理
  3. 登入業務處理

    a. 通過使用者名去db查詢使用者資訊

    b. 判斷是否查詢到用資訊

    c. 判斷是否被禁用

    d. 校驗密碼是否正确

    e. 生成token、把token做為key、userId作為value存入redis并設定過期時間為60分鐘(後面認證需要)

    f. 封裝傳回資料LoginRespVO 傳回前端

擷取使用者詳情接口

這個接口有兩個關鍵點,因為使用者首次登入後,後續再通路我們的系統資源的時候,無需再傳入使用者密碼進行驗證隻需要攜帶登入生成的token可以了,我們的後端會圍繞token使用shiro進行一系列的認證。當使用者通過了使用者認證的時候還需要進行授權,因為使用者詳解接口設定了通路權限(@RequiresPermissions(“sys:user:detail”))是以我們還要對通路的使用者進行授權。