SpringBoot整合Shiro验证Jwt

Shiro认证流程源码分析
subject.login() ——>  DelegatingSubject.login() ——> DefaultSecurityManager.login()  ——>  AbstractAuthenticator的authenticate()  ——>  ModularRealmAuthentiscat的doAuthenticate()  ——>  AuthenticatingRealm的抽象方法doGetAuthenticationInfo()  ——>  自定义Realm重写doGetAuthenticationInfo(需继承AuthorizingRealm,AuthorizingRealm继承自AuthenticatingRealm)进行认证

DelegatingSubject 源码如下:

public class DelegatingSubject implements Subject {

......
    
public void login(AuthenticationToken token) throws AuthenticationException {
        this.clearRunAsIdentitiesInternal();
    	// 此处继续调用login() -> DefaultSecurityManager.login()
        Subject subject = this.securityManager.login(this, token);
 	// ...略
}

DefaultSecurityManager 的 login() 源码如下:

public class DefaultSecurityManager extends SessionsSecurityManager {   
    
......	
    
    public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
        AuthenticationInfo info;
        try {
            //此处会继续调用AbstractAuthenticator的authenticate()方法
            info = this.authenticate(token);
        } catch (AuthenticationException var7) {
            AuthenticationException ae = var7;

            try {
                this.onFailedLogin(token, ae, subject);
            } catch (Exception var6) {
                if (log.isInfoEnabled()) {
                    log.info("onFailedLogin method threw an exception.  Logging and propagating original AuthenticationException.", var6);
                }
            }

            throw var7;
        }

        Subject loggedIn = this.createSubject(token, info, subject);
        this.onSuccessfulLogin(token, info, loggedIn);
        return loggedIn;
    }
}

AbstractAuthenticatorauthenticate 源码如下:

public abstract class AbstractAuthenticator implements Authenticator, LogoutAware {
	
    ......
    
    public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
        if (token == null) {
            throw new IllegalArgumentException("Method argument (authentication token) cannot be null.");
        } else {
            log.trace("Authentication attempt received for token [{}]", token);

            AuthenticationInfo info;
            try {
                // 调用自己的抽象方法
                info = this.doAuthenticate(token);
               // ......略
            return info;
        }
    }
    
    protected abstract AuthenticationInfo doAuthenticate(AuthenticationToken var1) throws AuthenticationException;
}

ModularRealmAuthentiscator 实现了 doAuthenticate ,源码如下:

public class ModularRealmAuthenticator extends AbstractAuthenticator {
	......
    	//1.得到自己注册的Realm类
        //2.判断Realm的个数,然后执行doSingleRealmAuthentication或者doMultiRealmAuthentication
        //3.在定义了多个Realm的情况下,可以自定义一个类继承ModularRealmAuthenticator,重写doAuthenticate,然后根据不同的类型调用不同的Realm
    protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
    	this.assertRealmsConfigured();
    	Collection<Realm> realms = this.getRealms();
    	return realms.size() == 1 ? 		this.doSingleRealmAuthentication((Realm)realms.iterator().next(), authenticationToken) : this.doMultiRealmAuthentication(realms, authenticationToken);
	}
}
Shiro整合jwt实现权限认证和授权
自定义JwtUtils工具类

主要用于对token的生成和校验

@Slf4j
public class JwtUtils {

    private static final String sign = "test$";

    /**
     * 描述:生成token
     *
     * @author zzy
     * @date 2022/10/26 15:06
     * @param claims 载荷,使用Sting-Object形式传入Map集合
     */
    public static String getToken(Map<String,Object> claims) {
        log.info("getToken claims:{}",claims);
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.DATE ,1);
        String token = JWT.create()
                .withClaim("user", claims)
                .withExpiresAt(calendar.getTime())
                .sign(Algorithm.HMAC256(sign));
        log.info("token success : {}",token);
        return token;
    }

    /**
     * 描述:校验token,并拿到载荷
     *
     * @author zzy
     * @date 2022/10/26 15:07
     * @param token 传入需要校验的token
     */
    public static Claim getClaim(String token) {
        DecodedJWT decodedJWT = JWT.decode(token);
        Claim claim;
        try {
            claim = decodedJWT.getClaim("user");
            return claim;
        }catch (JWTDecodeException e) {
            e.printStackTrace();
            log.debug("claims is null");
        }
        return null;
    }

    /**
     * 描述:校验token是否过期
     *
     * @author zzy
     * @date 2022/10/26 15:17
     * @param token 传入要校验的token
     */
    public static boolean verifyToken(String token) {
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(sign)).build();
        try {
            jwtVerifier.verify(token);
        } catch (TokenExpiredException e) {
            e.printStackTrace();
            log.info("token:{} 已过期",token);
            throw e;
        } catch (JWTVerificationException e) {
            log.error("token:{} 无效",token);
            throw e;
        }
        return true;
    }
}
自定义 JwtToken

JwtToken继承 AuthenticationToken ,用于subject.login()的入参

public class JwtToken implements AuthenticationToken {

    private String token;

    public JwtToken() {
    }

    public JwtToken(String token) {
        this.token = token;
    }

    @Override
    public Object getCredentials() {
        return token;
    }

    @Override
    public Object getPrincipal() {
        return token;
    }
}
自定义JwtTokenFilter

继承 AccessControlFilter ,用于拦截所用请求,并通过subjet.login() 调用自定义 realm 中的认证方法来校验 token

public class JwtTokenFilter extends AccessControlFilter {

    /**
     *	若此方法返回true,则不会执行onAccessDenied()
     *
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {
        HttpServletRequest req = (HttpServletRequest) servletRequest;
        // 解决跨域问题,放行options请求
        return HttpMethod.OPTIONS.toString().matches(req.getMethod());
    }

    /**
     * 描述:拦截所有请求,用shiro进行token认证
     *
     * @author zzy
     * @date 2022/10/26 16:37
     */
    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        if (haveTokenHeader(request,servletResponse)) {
            String token = request.getHeader("Authorization");
            try {
                //调用自定义realm,传入自定义JwtToken,校验token
                getSubject(request, servletResponse).login(new JwtToken(token));
                return true;
            }catch (Exception ignored){

            }
        }
        onLoginFail(servletResponse);
        return false;
    }

    /**
     * 描述:判断请求头中是否带有token字段
     *
     * @author zzy
     * @date 2022/10/26 16:22
     */
    private boolean haveTokenHeader(HttpServletRequest request, ServletResponse servletResponse) {
        String authentication = request.getHeader("Authorization");
        return authentication != null;
    }

    /**
     * 描述:登录失败
     *
     * @author zzy
     * @date 2022/10/26 16:51
     */
    private void onLoginFail(ServletResponse response) throws IOException {
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        httpResponse.setContentType("application/json;charset=utf-8");
        Map<String,Object> result = new HashMap<>();
        result.put("code", HttpServletResponse.SC_UNAUTHORIZED);
        result.put("msg", "login fail");
        ObjectMapper objectMapper = new ObjectMapper();
        httpResponse.getWriter().write(objectMapper.writeValueAsString(result));
    }
}
自定义 TokenRealm

实现token的校验以及对该用户授权

public class TokenRealm extends AuthorizingRealm {

    /**
     * 描述:防止自定义的JwtToken不属于AuthenticationToken
     *
     * @author zzy
     * @date 2022/10/26 17:15
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtToken;
    }

    /**
     * 描述:授权
     *
     * @author zzy
     * @date 2022/10/26 15:37
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        Set<String> roles = new HashSet<>();
        //从数据中查询然后添加角色和权限
        //roles.add("admin");
        simpleAuthorizationInfo.setRoles(roles);
        return simpleAuthorizationInfo;
    }

    /**
     * 描述:认证
     *
     * @author zzy
     * @date 2022/10/26 15:37
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String token = (String) authenticationToken.getPrincipal();
        boolean verifyToken;
        try {
            verifyToken = JwtUtils.verifyToken(token);
        } catch (TokenExpiredException e) {
            throw new ExpiredCredentialsException();
        } catch (JWTVerificationException e) {
            throw new IncorrectCredentialsException();
        }
        return verifyToken ? new SimpleAuthenticationInfo(token, token, this.getName()) : null;
    }
}
自定义**subject工厂**

禁用session,达到无状态

/**
 * 描述:自定义subject工厂,禁用session
 *
 * @author zzy
 * @date 2022/10/26 19:22
 */
public class BanSessionSubjectFactory extends DefaultWebSubjectFactory {

    @Override
    public Subject createSubject(SubjectContext context) {
        context.setSessionCreationEnabled(false);
        return super.createSubject(context);
    }
}
配置ShiroConfig

需要实现以下几点:

  1. 注册全局安全管理器DefaultWebSecurityManager
  2. 在DefaultWebSecurityManager注册自定义的Realm
  3. 在DefaultWebSecurityManager注册自定义的subject工厂,禁用session,禁用remember me
  4. 注册ShiroFilterFactoryBean,使用自定的Filter拦截路径校验token
@Configuration
public class ShiroConfig {

    /**
     * 描述:注册tokenRealm
     *
     * @author zzy
     * @date 2022/10/26 16:53
     */
    @Bean("realms")
    public Realm realms() {
        return new TokenRealm();
    }

    /**
     * 描述:注册subject工厂
     *
     * @author zzy
     * @date 2022/10/26 19:23
     */
    @Bean("subjectFactory")
    public DefaultWebSubjectFactory subjectFactory() {
        return new BanSessionSubjectFactory();
    }

    /**
     * 描述:注册全局安全管理器
     *
     * @author zzy
     * @date 2022/10/26 16:53
     */
    @Bean
    public DefaultWebSecurityManager defaultWebSecurityManager(Realm realm,DefaultWebSubjectFactory subjectFactory) {
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        defaultWebSecurityManager.setRealm(realm);

        //注册subject工厂
        defaultWebSecurityManager.setSubjectFactory(subjectFactory);
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        //禁用session存储
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        defaultWebSecurityManager.setSubjectDAO(subjectDAO);
        //禁用remember me
        defaultWebSecurityManager.setRememberMeManager(null);
        return defaultWebSecurityManager;
    }

    /**
     * 描述:注册shiro过滤器
     *
     * @author zzy
     * @date 2022/10/26 16:55
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
        //注册过滤器
        Map<String, Filter> filterMap = new LinkedHashMap<>();
        filterMap.put("jwt", new JwtTokenFilter());
        Map<String, String> filters = new HashMap<>();
        filters.put("/login", "anon");
        //不再使用shiro内置的authc过滤器
        filters.put("/**", "jwt");
        shiroFilterFactoryBean.setFilters(filterMap);
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filters);
        return shiroFilterFactoryBean;
    }
}
配置统一异常配置类捕获认证异常、权限异常
@ControllerAdvice
@RestController
public class ExceptionHandler {

    /**
     * 描述:捕获权限不足异常
     *
     * @author zzy
     * @date 2022/10/26 22:15
     */
    @org.springframework.web.bind.annotation.ExceptionHandler(value = UnauthorizedException.class)
    public Map<String,Object> UnauthorizedException() {
        Map<String,Object> result = new HashMap<>();
        result.put("code", 405);
        result.put("msg", "权限不足!");
        return result;
    }
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值