shiro+JWT实现简单认证

首先定义一个ShiroConfig配置类,在此类里面创建sessionManager和securityManager对象,并把他们放入IOC中,然后对自定义的过滤器进行定义,主要就是把定义的filter放入到过滤器链中

	@Bean
    public ShiroFilterChainDefinition shiroFilterChainDefinition() {
        DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();
        Map<String, String> filterMap = new LinkedHashMap<>();
        filterMap.put("/**", "jwt"); // 主要通过注解方式校验权限   使用jwtFilter进行过滤
        chainDefinition.addPathDefinitions(filterMap);
        return chainDefinition;
    }

然后创建shiroFilterFactoryBean,FactoryBean需要看他的getObject方法

	public Object getObject() throws Exception {
        if (instance == null) {
            instance = createInstance();
        }
        return instance;
	}

然后进入createInstance方法

	protected AbstractShiroFilter createInstance() throws Exception {
        logdebug("Creating Shiro Filter instance.");
        SecurityManager securityManager = getSecurityManager();
        //省略部分代码
        //创建FilterChain管理器,会将Shiro默认的Filter加入进来,同时将配置文件中的Filter加进来
        FilterChainManager manager = createFilterChainManager();
        PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
        chainResolver.setFilterChainManager(manager);
        return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
	}

此factoryBean的作用,只需要把自定义好的filter放入shiroFilterFactoryBean中就可以在程序运行中执行自定义的filter

	@Bean("shiroFilterFactoryBean")
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager,
                                                         ShiroFilterChainDefinition shiroFilterChainDefinition) {
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(securityManager);
        Map<String, Filter> filters = new HashMap<>();
        filters.put("jwt", jwtFilter);
        shiroFilter.setFilters(filters);
        Map<String, String> filterMap = shiroFilterChainDefinition.getFilterChainMap();
        shiroFilter.setFilterChainDefinitionMap(filterMap);
        return shiroFilter;
    }

这里出现的jwtFilter就是自定义的filter继承AuthenticatingFilter,他主要的作用就是对每一次请求进行拦截,做一些校验

@Component
public class JwtFilter extends AuthenticatingFilter {

    @Autowired
    JwtUtils jwtUtils;

    @Override
    protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {

        HttpServletRequest request = (HttpServletRequest) servletRequest;
        String jwt = request.getHeader("Authorization");
        if (StringUtils.isEmpty(jwt)) {
            return null;
        }
        return new JwtToken(jwt);
    }


    /*
    * 进行拦截,如果没有token则不走Realm,直接访问controller,若无权限判断则访问成功,若有权限判断则访问失败
    * */
    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        String jwt = request.getHeader("Authorization");
        if (StringUtils.isEmpty(jwt)) {
            return true;
        }else {
            //校验jwt
            Claims claim = jwtUtils.getClaimByToken(jwt);
            if (ObjectUtils.isEmpty(claim) || jwtUtils.isTokenExpired(claim.getExpiration())) {
                throw new ExpiredCredentialsException("token失效,请重新登录");
            }
            //执行登录
            return executeLogin(servletRequest,servletResponse);
        }
    }

    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        Throwable throwable = e.getCause() == null ? e : e.getCause();
        Result result = Result.fail(throwable.getMessage());
        String json = JSONUtil.toJsonStr(result);
        try {
            httpServletResponse.getWriter().print(json);
        } catch (IOException ex) {

        }
        return false;
    }

    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
        HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
        httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
        httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
        // 跨域时会首先发送一个OPTIONS请求,这里我们给OPTIONS请求直接返回正常状态
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(org.springframework.http.HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }
}

createToken方法是从请求中获取token信息,然后对String类型的token进行转换成JwtToken
onAccessDenied方法是真正的拦截方法,判断请求是否带有token,没有token即为未登录状态,有token的为登录状态,若没有token直接放行,因为游客也是有一定的权限的比如登录权限…若有token则需要判断这个token师傅合法是否失效等,最后调用executeLogin方法,即为提交给登录,这里就到了shiro著名的Realm中,源码是

	protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        AuthenticationToken token = this.createToken(request, response);
        if (token == null) {
            String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken must be created in order to execute a login attempt.";
            throw new IllegalStateException(msg);
        } else {
            try {
                Subject subject = this.getSubject(request, response);
                subject.login(token);
                return this.onLoginSuccess(token, subject, request, response);
            } catch (AuthenticationException var5) {
                return this.onLoginFailure(token, var5, request, response);
            }
        }
    }

在源码中发现调用成功会调用onLoginSucccess方法,而失败的onLoginFailure方法需要在Filter类中重写,目的是统一封装返回格式为Result
最后的preHandle是解决跨区问题的
然后再看createToken方法中把String对象封装成JwtToken的类,只需要实现AuthenticationToken

public class JwtToken implements AuthenticationToken {

    private String token;

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

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

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

最后,自定义的Realm,总结来说就是继承AuthorizingRealm,然后重写认证doGetAuthorizationInfo方法,从token中获取user信息,查询数据库比对信息,若为正常用户,则把用户的信息封装到SimpleAuthorizationInfo对象中

@Slf4j
@Component
public class AccountRealm extends AuthorizingRealm {

    @Autowired
    JwtUtils jwtUtils;

    @Autowired
    UserService userService;

    /*
    * 告知shoir,token支持JwtToken
    * */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtToken;
    }

    /*
    * 授权
    * */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    /*
    * 认证
    * */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

        //需要调用supports
        JwtToken jwtToken = (JwtToken) authenticationToken;

        String userId = jwtUtils.getClaimByToken((String) jwtToken.getPrincipal()).getSubject();
        User user = userService.getById(Long.valueOf(userId));

        if (ObjectUtils.isEmpty(user)) {
            throw new UnknownAccountException("用户不存在");
        }

        if (user.getStatus() == -1) {
            throw new LockedAccountException("账户已被锁定");
        }

        AccountProfile profile = new AccountProfile();
        BeanUtil.copyProperties(user, profile);

        //公开信息
        return new SimpleAuthenticationInfo(profile, jwtToken.getCredentials(), getName());
    }
}

这里值得说明的是在Reaml的认证方法中不需要对username进行比对,因为这都是login接口需要做的

你可能感兴趣的:(shiro)