天天看點

Shiro 實作免密登陸

需求:對接第三方登陸,實作繞過原有Shiro認證登陸。

文章目錄

  • ​​一、實作思路​​
  • ​​1. 現狀分析​​
  • ​​2. 使用者來源​​
  • ​​3. 所屬範圍​​
  • ​​二、實作方案​​
  • ​​2.1. 自定義登入認證規則​​
  • ​​2.2. Shiro認證枚舉​​
  • ​​2.3. 密碼和非密碼登入​​
  • ​​2.4. 規則配置​​
  • ​​2.5. 自定義Realm​​
  • ​​2.6. 案例使用​​
一、實作思路

1. 現狀分析

系統權架構預設使用Shiro 認證授權機制

2. 使用者來源

從統一認證平台登入跳轉過來的使用者

3. 所屬範圍

二、實作方案

2.1. 自定義登入認證規則

package com.gblfy.config.skipshiro;

import com.gblfy.config.skipshiro.enums.ShiroApproveLoginType;
import org.apache.shiro.authc.UsernamePasswordToken;

/**
 * 自定義token 實作免密和密碼登入
 * <p>
 * 1.賬号密碼登陸(password)
 * 2.免密登陸(nopassword)
 * </p>
 *
 * @author gblfy
 * @date 2021-10-22
 */
public class EasyUsernameToken extends UsernamePasswordToken {
    private static final long serialVersionUID = -2564928913725078138L;

    private ShiroApproveLoginType type;

    public EasyUsernameToken() {
        super();
    }

    /**
     * 免密登入
     */
    public EasyUsernameToken(String username) {
        super(username, "", false, null);
        this.type = ShiroApproveLoginType.NOPASSWD;
    }

    /**
     * 賬号密碼登入
     */
    public EasyUsernameToken(String username, String password, boolean rememberMe) {
        super(username, password, rememberMe, null);
        this.type = ShiroApproveLoginType.PASSWORD;
    }

    public ShiroApproveLoginType getType() {
        return type;
    }

    public void setType(ShiroApproveLoginType type) {
        this.type = type;
    }

}      

2.2. Shiro認證枚舉

package com.gblfy.config.skipshiro.enums;

/**
 * Shiro認證枚舉
 * @author gblfy
 * @date 2021-10-22
 */
public enum ShiroApproveLoginType {
    /** 密碼登入 */
    PASSWORD("PASSWORD"),
    /** 密碼登入 */
    NOPASSWD("NOPASSWORD");
    /** 狀态值 */
    private String code;
    private ShiroApproveLoginType(String code) {
        this.code = code;
    }

    public String getCode() {
        return code;
    }
}      

2.3. 密碼和非密碼登入

package com.gblfy.config.skipshiro;

import com.gblfy.config.skipshiro.enums.ShiroApproveLoginType;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;

/**
 * 自定義登入認證方案
 * <p>
 * 1.免密登入,不加密
 * 2.密碼登入,md5加密
 * </p>
 *
 * @author gblfy
 * @date 2021-10-22
 */
public class EasyCredentialsMatch extends HashedCredentialsMatcher {

    /**
     * 重寫方法
     * 區分 密碼和非密碼登入
     * 此次無需記錄登入次數 詳情看SysPasswordService
     */
    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        EasyUsernameToken easyUsernameToken = (EasyUsernameToken) token;

        //免密登入,不驗證密碼
        if (ShiroApproveLoginType.NOPASSWD.equals(easyUsernameToken.getType())) {
            return true;
        }

        //密碼登入
        Object tokenHashedCredentials = hashProvidedCredentials(token, info);
        Object accountCredentials = getCredentials(info);
        return equals(tokenHashedCredentials, accountCredentials);
    }
}      

2.4. 規則配置

customCredentialsMatch() {
        EasyCredentialsMatch customCredentialsMatch = new EasyCredentialsMatch();
        customCredentialsMatch.setHashAlgorithmName("md5");
        customCredentialsMatch.setHashIterations(3);
        customCredentialsMatch.setStoredCredentialsHexEncoded(true);
        return customCredentialsMatch;
    }      

2.5. 自定義Realm

public class UserRealm extends AuthorizingRealm {

    /**
     * 權限認證  
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
      //權限認證  代碼省略
    }

   /**
     * 登入認證
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        EasyUsernameToken upToken = (EasyUsernameToken) token;
        String username = upToken.getUsername();

        SysUser user = null;
        // 密碼登入
        if (upToken.getType().getCode().equals(LoginType.PASSWORD.getCode())) {
            String password;
            if (upToken.getPassword() != null) {
                password = new String(upToken.getPassword());
                try {
                    user = loginService.login(username, password);
                } 
                catch (Exception e) {
                    log.info("對使用者[" + username + "]進行登入驗證..驗證未通過{}", e.getMessage());
                    throw new AuthenticationException(e.getMessage(), e);
                }
            }
        } else if (upToken.getType().getCode().equals(LoginType.NOPASSWD.getCode())) {
            // 第三方登入 TODO
         
        }

        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, upToken.getPassword(), getName());
        return info;
    }
}      

2.6. 案例使用

(String username, String password, Boolean rememberMe) {
        EasyUsernameToken token = new EasyUsernameToken(username, password, rememberMe);
        Subject subject = SecurityUtils.getSubject();
        try {
            subject.login(token);
            return success();
        } catch (AuthenticationException e) {
            String msg = "使用者或密碼錯誤";
            if (StringUtils.isNotEmpty(e.getMessage())) {
                msg = e.getMessage();
            }
            return error(msg);
        }
    }