Shiro 实现免密登陆

需求:对接第三方登陆,实现绕过原有Shiro认证登陆。

一、实现思路
1. 现状分析

系统权框架默认使用Shiro 认证授权机制

2. 用户来源

从统一认证平台登录跳转过来的用户

3. 所属范围

登录限制由统一认证平台去做,但是,跳转过来的用户仍然走您本系统的登录流程,只是走本系统的登录流程时,想跳过Shiro 对用户密码的校验,校验所属范围为Shiro 认证机制,其他功能照旧;

二、实现方案
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. 规则配置
    @Bean
    public EasyCredentialsMatch 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. 案例使用
    public AjaxResult login(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);
        }
    }