Shrio使用Jwt达到前后端分离

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> Shrio使用Jwt达到前后端分离

概述

前后端分离之后,因为HTTP本身是无状态的,Session就没法用了。项目采用jwt的方案后,请求的主要流程如下:用户登录成功之后,服务端会创建一个jwt的token(jwt的这个token中记录了当前的操作账号),并将这个token返回给前端,前端每次请求服务端的数据时,都会将令牌放入Header或者Parameter中,服务端接收到请求后,会先被拦截器拦截,token检验的拦截器会获取请求中的token,然后会检验token的有效性,拦截器都检验成功后,请求会成功到达实际的业务流程中,执行业务逻辑返回给前端数据。在这个过程中,主要涉及到Shiro的拦截器链,Jwt的token管理,多Realm配置等。

Shiro的Filter链

Shiro的认证和授权都离不开Filter,因此需要对Shiro的Filter的运行流程很清楚,才能自定义Filter来满足企业的实际需要。另外Shiro的Filter虽然原理都和Servlet的Filter相似,甚至都最终继承相同的接口,但是实际还是有些差别。Shiro中的Filter主要是在ShiroFilter内,对指定匹配的URL进行拦截处理,它有自己的Filter链;而Servlet的Filter和ShiroFilter是同一个级别的,即先走Shiro自己的Filter体系,然后才会委托给Servlet容器的FilterChain进行Servlet容器级别的Filter链执行

分析Shiro的默认Filter

在Shiro和Spring Boot整合过程中,需要配置 ShiroFilterFactoryBean,该类是 ShiroFilter的工厂类,并继承了 FactoryBean接口。可以从该接口的方法来分析。该接口 getObject获取一个实例,按照逻辑,发现调用 createFilterChainManager,并创建默认的Filter(按照命名猜测 MapString, Filter defaultFilters = manager.getFilters())。

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
public class ShiroFilterFactoryBean implements FactoryBean, BeanPostProcessor {    private MapString, Filter filters;     private MapString, String filterChainDefinitionMap;      /**     *     * 该工厂类生产的产品类     */    public Object getObject() throws Exception {        if (instance == null) {            instance = createInstance();        }        return instance;    }     protected FilterChainManager createFilterChainManager() {        //创建默认Filter        DefaultFilterChainManager manager = new DefaultFilterChainManager();        MapString, Filter defaultFilters = manager.getFilters();        for (Filter filter : defaultFilters.values()) {            applyGlobalPropertiesIfNecessary(filter);        }         MapString, Filter filters = getFilters();        if (!CollectionUtils.isEmpty(filters)) {            for (Map.EntryString, Filter entry : filters.entrySet()) {                String name = entry.getKey();                Filter filter = entry.getValue();                applyGlobalPropertiesIfNecessary(filter);                if (filter instanceof Nameable) {                    ((Nameable) filter).setName(name);                }                manager.addFilter(name, filter, false);            }        }         MapString, String chains = getFilterChainDefinitionMap();        if (!CollectionUtils.isEmpty(chains)) {            for (Map.EntryString, String entry : chains.entrySet()) {                String url = entry.getKey();                String chainDefinition = entry.getValue();                manager.createChain(url, chainDefinition);            }        }         return manager;    }     protected AbstractShiroFilter createInstance() throws Exception {         log.debug("Creating Shiro Filter instance.");         SecurityManager securityManager = getSecurityManager();        if (securityManager == null) {            String msg = "SecurityManager property must be set.";            throw new BeanInitializationException(msg);        }         if (!(securityManager instanceof WebSecurityManager)) {            String msg = "The security manager does not implement the WebSecurityManager interface.";            throw new BeanInitializationException(msg);        }        //创建FilterChainManager        FilterChainManager manager = createFilterChainManager();         PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();        chainResolver.setFilterChainManager(manager);         return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);    }    ...}

public class ShiroFilterFactoryBean implements FactoryBean, BeanPostProcessor {
private MapString, Filter filters;


private MapString, String filterChainDefinitionMap; 

/**
 *
 * 该工厂类生产的产品类
 */
public Object getObject() throws Exception {
    if (instance == null) {
        instance = createInstance();
    }
    return instance;
}

protected FilterChainManager createFilterChainManager() {
    //创建默认Filter
    DefaultFilterChainManager manager = new DefaultFilterChainManager();
    MapString, Filter defaultFilters = manager.getFilters();
    for (Filter filter : defaultFilters.values()) {
        applyGlobalPropertiesIfNecessary(filter);
    }

    MapString, Filter filters = getFilters();
    if (!CollectionUtils.isEmpty(filters)) {
        for (Map.EntryString, Filter entry : filters.entrySet()) {
            String name = entry.getKey();
            Filter filter = entry.getValue();
            applyGlobalPropertiesIfNecessary(filter);
            if (filter instanceof Nameable) {
                ((Nameable) filter).setName(name);
            }
            manager.addFilter(name, filter, false);
        }
    }

    MapString, String chains = getFilterChainDefinitionMap();
    if (!CollectionUtils.isEmpty(chains)) {
        for (Map.EntryString, String entry : chains.entrySet()) {
            String url = entry.getKey();
            String chainDefinition = entry.getValue();
            manager.createChain(url, chainDefinition);
        }
    }

    return manager;
}

protected AbstractShiroFilter createInstance() throws Exception {

    log.debug("Creating Shiro Filter instance.");

    SecurityManager securityManager = getSecurityManager();
    if (securityManager == null) {
        String msg = "SecurityManager property must be set.";
        throw new BeanInitializationException(msg);
    }

    if (!(securityManager instanceof WebSecurityManager)) {
        String msg = "The security manager does not implement the WebSecurityManager interface.";
        throw new BeanInitializationException(msg);
    }
    //创建FilterChainManager
    FilterChainManager manager = createFilterChainManager();

    PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
    chainResolver.setFilterChainManager(manager);

    return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
}


}

DefaultFilterChainManager addDefaultFilters来添加默认的Filter,DefaultFilter为一系列默认Filter的枚举类。

123456789101112131415161718192021222324252627282930
public class DefaultFilterChainManager implements FilterChainManager {     public MapString, Filter getFilters() {        return filters;    }     protected void addFilter(String name, Filter filter, boolean init, boolean overwrite) {        Filter existing = getFilter(name);        if (existing == null || overwrite) {            if (filter instanceof Nameable) {                ((Nameable) filter).setName(name);            }            if (init) {                initFilter(filter);            }            this.filters.put(name, filter);        }    }      /**     *     * 创建默认的Filter     */    protected void addDefaultFilters(boolean init) {        for (DefaultFilter defaultFilter : DefaultFilter.values()) {            addFilter(defaultFilter.name(), defaultFilter.newInstance(), init, false);        }    }    ...}

public class DefaultFilterChainManager implements FilterChainManager {


public MapString, Filter getFilters() {
    return filters;
}

protected void addFilter(String name, Filter filter, boolean init, boolean overwrite) {
    Filter existing = getFilter(name);
    if (existing == null || overwrite) {
        if (filter instanceof Nameable) {
            ((Nameable) filter).setName(name);
        }
        if (init) {
            initFilter(filter);
        }
        this.filters.put(name, filter);
    }
}

 /**
 *
 * 创建默认的Filter
 */
protected void addDefaultFilters(boolean init) {
    for (DefaultFilter defaultFilter : DefaultFilter.values()) {
        addFilter(defaultFilter.name(), defaultFilter.newInstance(), init, false);
    }
}
...

}

从这个枚举类中可以看到之前添加的共有11个默认Filter,它们的名字分别是anon,authc,authcBaisc等。

1234567891011121314151617181920212223242526272829
public enum DefaultFilter {     anon(AnonymousFilter.class),    authc(FormAuthenticationFilter.class),    authcBasic(BasicHttpAuthenticationFilter.class),    logout(LogoutFilter.class),    noSessionCreation(NoSessionCreationFilter.class),    perms(PermissionsAuthorizationFilter.class),    port(PortFilter.class),    rest(HttpMethodPermissionFilter.class),    roles(RolesAuthorizationFilter.class),    ssl(SslFilter.class),    user(UserFilter.class);     private final Class? extends Filter filterClass;     private DefaultFilter(Class? extends Filter filterClass) {        this.filterClass = filterClass;    }     public Filter newInstance() {        return (Filter) ClassUtils.newInstance(this.filterClass);    }     public Class? extends Filter getFilterClass() {        return this.filterClass;    }    ...}

public enum DefaultFilter {


anon(AnonymousFilter.class),
authc(FormAuthenticationFilter.class),
authcBasic(BasicHttpAuthenticationFilter.class),
logout(LogoutFilter.class),
noSessionCreation(NoSessionCreationFilter.class),
perms(PermissionsAuthorizationFilter.class),
port(PortFilter.class),
rest(HttpMethodPermissionFilter.class),
roles(RolesAuthorizationFilter.class),
ssl(SslFilter.class),
user(UserFilter.class);

private final Class? extends Filter filterClass;

private DefaultFilter(Class? extends Filter filterClass) {
    this.filterClass = filterClass;
}

public Filter newInstance() {
    return (Filter) ClassUtils.newInstance(this.filterClass);
}

public Class? extends Filter getFilterClass() {
    return this.filterClass;
}
...

}

Filter的继承体系分析

  • NameableFilter给Filter起个名字,如果没有设置,默认名字就是FilterName。
  • OncePerRequestFilter用于防止多次执行Filter;也就是说一次请求只会走一次拦截器链;另外提供 enabled 属性,表示是否开启该拦截器实例,默认 enabled=true 表示开启,如果不想让某个拦截器工作,可以设置为 false 即可。
  • AdviceFilter提供了AOP风格的支持。preHandler:在拦截器链执行之前执行,如果返回true则继续拦截器链;否则中断后续的拦截器链的执行直接返回;可以进行预处理(如身份验证、授权等行为)。postHandle:在拦截器链执行完成后执行,后置处理(如记录执行时间之类的)。afterCompletion:类似于AOP中的后置最终增强;即不管有没有异常都会执行,可以进行清理资源(如接触 Subject 与线程的绑定之类的)。
  • PathMatchingFilter内置了pathMatcher的实例,方便对请求路径匹配功能及拦截器参数解析的功能,如下所示,对匹配的路径执行 isFilterChainContinued的逻辑,如果都没配到,则直接交给拦截器链。
    1234567891011121314151617181920
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {     if (this.appliedPaths == null || this.appliedPaths.isEmpty()) {        if (log.isTraceEnabled()) {            log.trace("appliedPaths property is null or empty.  This Filter will passthrough immediately.");        }        return true;    }     for (String path : this.appliedPaths.keySet()) {        //对匹配路径进行处理        if (pathsMatch(path, request)) {            log.trace("Current requestURI matches pattern '{}'.  Determining filter chain execution...", path);            Object config = this.appliedPaths.get(path);            return isFilterChainContinued(request, response, path, config);        }    }     return true;}

protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {


if (this.appliedPaths == null || this.appliedPaths.isEmpty()) {
    if (log.isTraceEnabled()) {
        log.trace("appliedPaths property is null or empty.  This Filter will passthrough immediately.");
    }
    return true;
}

for (String path : this.appliedPaths.keySet()) {
    //对匹配路径进行处理
    if (pathsMatch(path, request)) {
        log.trace("Current requestURI matches pattern '{}'.  Determining filter chain execution...", path);
        Object config = this.appliedPaths.get(path);
        return isFilterChainContinued(request, response, path, config);
    }
}

return true;

}

  • AccessControlFilter提供了访问控制的基础功能,isAccessAllowed访问通过,则交给拦截器链,不通过则执行 onAccessDenied来确定交给拦截器还是自己处理
    123
    public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {        return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);    }

public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
}

  • AuthenticationFilter认证Filter的基类,一般在isAccessAllowed中执行认证逻辑,另外该Filter提供登录成功后跳转的功能
    123456789
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object      mappedValue) {    Subject subject = getSubject(request, response);    return subject.isAuthenticated();}  protected void issueSuccessRedirect(ServletRequest request, ServletResponse response) throws        Exception {    WebUtils.redirectToSavedRequest(request, response, getSuccessUrl());}

protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
Subject subject = getSubject(request, response);
return subject.isAuthenticated();
}

protected void issueSuccessRedirect(ServletRequest request, ServletResponse response) throws Exception {
WebUtils.redirectToSavedRequest(request, response, getSuccessUrl());
}

  • AuthenticatingFilter是AuthenticationFilter的子类,提供了 executeLogin通用逻辑,通常由子类来实现 protected abstract AuthenticationToken createToken(ServletRequest request, ServletResponse response)该方法,然后执行 subject.login(token)
    1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
    public abstract class AuthenticatingFilter extends AuthenticationFilter {    public static final String PERMISSIVE = "permissive";     protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {        AuthenticationToken token = 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);        }        try {            Subject subject = getSubject(request, response);            subject.login(token);            return onLoginSuccess(token, subject, request, response);        } catch (AuthenticationException e) {            return onLoginFailure(token, e, request, response);        }    }     protected abstract AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception;     protected AuthenticationToken createToken(String username, String password,                                              ServletRequest request, ServletResponse response) {        boolean rememberMe = isRememberMe(request);        String host = getHost(request);        return createToken(username, password, rememberMe, host);    }     protected AuthenticationToken createToken(String username, String password,                                              boolean rememberMe, String host) {        return new UsernamePasswordToken(username, password, rememberMe, host);    }     protected boolean onLoginSuccess(AuthenticationToken token, Subject subject,                                     ServletRequest request, ServletResponse response) throws Exception {        return true;    }     protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e,                                     ServletRequest request, ServletResponse response) {        return false;    }     @Override    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {        return super.isAccessAllowed(request, response, mappedValue) ||            (!isLoginRequest(request, response) && isPermissive(mappedValue));    }    ...}

public abstract class AuthenticatingFilter extends AuthenticationFilter {
public static final String PERMISSIVE = “permissive”;


protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
    AuthenticationToken token = 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);
    }
    try {
        Subject subject = getSubject(request, response);
        subject.login(token);
        return onLoginSuccess(token, subject, request, response);
    } catch (AuthenticationException e) {
        return onLoginFailure(token, e, request, response);
    }
}

protected abstract AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception;

protected AuthenticationToken createToken(String username, String password,
                                          ServletRequest request, ServletResponse response) {
    boolean rememberMe = isRememberMe(request);
    String host = getHost(request);
    return createToken(username, password, rememberMe, host);
}

protected AuthenticationToken createToken(String username, String password,
                                          boolean rememberMe, String host) {
    return new UsernamePasswordToken(username, password, rememberMe, host);
}

protected boolean onLoginSuccess(AuthenticationToken token, Subject subject,
                                 ServletRequest request, ServletResponse response) throws Exception {
    return true;
}

protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e,
                                 ServletRequest request, ServletResponse response) {
    return false;
}

@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
    return super.isAccessAllowed(request, response, mappedValue) ||
        (!isLoginRequest(request, response) && isPermissive(mappedValue));
}
...

}

在Shiro中添加自定义的Filter

从上面源码分析,知道了Shiro会提供11个默认的Filter,也是按照拦截器模式交由FilterChainManager来管理Filter,并最终返回SpringShiroFilter。所以添加自定义的Filter,主要有三步。

  • 实现自己的Filter

如下实现了自己的JwtFilter,主要逻辑可以参考FormAuthenticationFilter。JwtFilter主要是对前端的Api进行校验,检验失败,则抛出异常信息,不给拦截器链处理。

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
@Slf4jpublic class JwtFilter extends AuthenticatingFilter {       private static final String TOKEN_NAME = "token";     /**     * 创建令牌     */    @Override    protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {        final String token = getToken((HttpServletRequest) servletRequest);             if(StringUtils.isEmpty(token)) {            return null;        }               return new JwtToken(token);         }     /**     * 获取令牌     * @param httpServletRequest     * @return     */    private String getToken(HttpServletRequest httpServletRequest) {        String token = httpServletRequest.getHeader(TOKEN_NAME);        if(StringUtils.isEmpty(token)) {            token = httpServletRequest.getParameter(TOKEN_NAME);        };        if(StringUtils.isEmpty(token)) {            Cookie[] cookies = httpServletRequest.getCookies();            if(ArrayUtils.isNotEmpty(cookies)) {                for(Cookie cookie :cookies) {                    if(TOKEN_NAME.equals(cookie.getName())) {                        token = cookie.getValue();                        break;                    }                }            }        };          return token;    }     /**     * 未通过处理     * @param servletRequest     * @param servletResponse     * @return     * @throws Exception     */    @Override    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {        return executeLogin(servletRequest, servletResponse);    }     /**     * 登录失败执行方法     * @param token     * @param e     * @param request     * @param response     * @return     */    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request,            ServletResponse response) {        response.setContentType("text/html;charset=UTF-8");        try(OutputStream outputStream = response.getOutputStream()){            outputStream.write(e.getMessage().getBytes(SystemConsts.CHARSET));            outputStream.flush();                   } catch (IOException e1) {            e1.printStackTrace();        }           return false;    }    ...}

@Slf4j
public class JwtFilter extends AuthenticatingFilter {
private static final String TOKEN_NAME = “token”;


/**
 * 创建令牌
 */
@Override
protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
    final String token = getToken((HttpServletRequest) servletRequest);     
    if(StringUtils.isEmpty(token)) {
        return null;
    }       
    return new JwtToken(token);     
}

/**
 * 获取令牌
 * @param httpServletRequest
 * @return
 */
private String getToken(HttpServletRequest httpServletRequest) {
    String token = httpServletRequest.getHeader(TOKEN_NAME);
    if(StringUtils.isEmpty(token)) {
        token = httpServletRequest.getParameter(TOKEN_NAME);
    };
    if(StringUtils.isEmpty(token)) {
        Cookie[] cookies = httpServletRequest.getCookies();
        if(ArrayUtils.isNotEmpty(cookies)) {
            for(Cookie cookie :cookies) {
                if(TOKEN_NAME.equals(cookie.getName())) {
                    token = cookie.getValue();
                    break;
                }
            }
        }
    };  
    return token;
}

/**
 * 未通过处理
 * @param servletRequest
 * @param servletResponse
 * @return
 * @throws Exception
 */
@Override
protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
    return executeLogin(servletRequest, servletResponse);
}

/**
 * 登录失败执行方法
 * @param token
 * @param e
 * @param request
 * @param response
 * @return
 */
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request,
        ServletResponse response) {
    response.setContentType("text/html;charset=UTF-8");
    try(OutputStream outputStream = response.getOutputStream()){
        outputStream.write(e.getMessage().getBytes(SystemConsts.CHARSET));
        outputStream.flush();           
    } catch (IOException e1) {
        e1.printStackTrace();
    }   
    return false;
}
...

}

  • 将Filter添加到Shiro中

将自定义的Filter添加到Shiro,并要指定的匹配路径。

12345678910111213
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Autowired          org.apache.shiro.mgt.SecurityManager securityManager, @Autowired JwtFilter jwtFilter) {        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();         MapString, Filter filterMap = new LinkedHashMap();        filterMap.put("jwt", jwtFilter);        shiroFilterFactoryBean.setFilters(filterMap);         MapString, String filterChainDefinitionMap = new LinkedHashMap();        filterChainDefinitionMap.put("/**", "jwt");         shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);    ...        return shiroFilterFactoryBean;    }

public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Autowired org.apache.shiro.mgt.SecurityManager securityManager, @Autowired JwtFilter jwtFilter) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();


    MapString, Filter filterMap = new LinkedHashMap();
    filterMap.put("jwt", jwtFilter);
    shiroFilterFactoryBean.setFilters(filterMap);

    MapString, String filterChainDefinitionMap = new LinkedHashMap();
    filterChainDefinitionMap.put("/**", "jwt"); 
    shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
...
    return shiroFilterFactoryBean;
}

注意:SpringBoot自动帮我们注册了我们的Filter(Filter是注册到整个Filter链,而不是Shiro的Filter链),但是在Shiro中,我们需要自己实现注册,但是又需要Filter实例存在于Spring容器中,以便能使用其他众多服务(自动注入其他组件……)。所以需要取消Spring Boot的自动注入Filter。可以采用如下方式:

123456
@Beanpublic FilterRegistrationBean registration(@Qualifier("devCryptoFilter") DevCryptoFilter filter){    FilterRegistrationBean registration = new FilterRegistrationBean(filter);    registration.setEnabled(false);    return registration;}

@Bean
public FilterRegistrationBean registration(@Qualifier(“devCryptoFilter”) DevCryptoFilter filter){
FilterRegistrationBean registration = new FilterRegistrationBean(filter);
registration.setEnabled(false);
return registration;
}

Jwt整合

使用Jwt需要我们提供对token的创建,校验和获取token中信息的方法。网上有很多,可以借鉴,而且token中也可以存一些其他数据。

1234567891011121314151617181920212223242526272829
public class JwtUtil {     /**     * 检验token     * @return boolean     */    public static boolean verify(String token, String username) {        ...    }     /**     * 获得token中的属性     * @return token中包含的属性     */    public static String getValue(String token, String key) {        ...    }     /**     * 生成token签名EXPIRE_TIME 分钟后过期     *      * @param username     *            用户名     * @return 加密的token     */    public static String createJWT(String userId) {        ...    }}

public class JwtUtil {


/**
 * 检验token
 * @return boolean
 */
public static boolean verify(String token, String username) {
    ...
}

/**
 * 获得token中的属性
 * @return token中包含的属性
 */
public static String getValue(String token, String key) {
    ...
}

/**
 * 生成token签名EXPIRE_TIME 分钟后过期
 * 
 * @param username
 *            用户名
 * @return 加密的token
 */
public static String createJWT(String userId) {
    ...
}

}

多Realm配置

用户密码认证和Jwt的认证需要不同的两个Realm,多Realm需要处理不同的Realm,获取到指定Realm的AuthenticationToken的数据模型。

  • 实现ModularRealmAuthenticator的方法
    123456789101112131415161718
    public class MultiRealmAuthenticator extends ModularRealmAuthenticator {     @Override    protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken)             throws AuthenticationException {        assertRealmsConfigured();         ListRealm realms = this.getRealms()                .stream()                .filter(realm - {                    return realm.supports(authenticationToken);                })                .collect(Collectors.toList());         return realms.size() == 1 ? this.doSingleRealmAuthentication(realms.get(0), authenticationToken) :             this.doMultiRealmAuthentication(realms, authenticationToken);    }}

public class MultiRealmAuthenticator extends ModularRealmAuthenticator {


@Override
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) 
        throws AuthenticationException {
    assertRealmsConfigured();
    
    ListRealm realms = this.getRealms()
            .stream()
            .filter(realm - {
                return realm.supports(authenticationToken);
            })
            .collect(Collectors.toList());
    
    return realms.size() == 1 ? this.doSingleRealmAuthentication(realms.get(0), authenticationToken) : 
        this.doMultiRealmAuthentication(realms, authenticationToken);
}

}

  • AuthenticatingRealm中实现 getAuthenticationTokenClass方法
    123
    public Class getAuthenticationTokenClass() {    return JwtToken.class;}

public Class getAuthenticationTokenClass() {
return JwtToken.class;
}

  • 在SecurityManager中配置
    1234567
    @Bean(name = "securityManager")public org.apache.shiro.mgt.SecurityManager defaultWebSecurityManager(@Autowired UserRealm      userRealm,  @Autowired TokenRealm tokenValidateRealm) {    securityManager.setAuthenticator(multiRealmAuthenticator());    securityManager.setRealms(Arrays.asList(userRealm, tokenValidateRealm));    ...    return securityManager;}

@Bean(name = “securityManager”)
public org.apache.shiro.mgt.SecurityManager defaultWebSecurityManager(@Autowired UserRealm userRealm, @Autowired TokenRealm tokenValidateRealm) {
securityManager.setAuthenticator(multiRealmAuthenticator());
securityManager.setRealms(Arrays.asList(userRealm, tokenValidateRealm));

return securityManager;
}

整合Swagger

添加Swagger依赖

1234567891011
dependency    groupIdio.springfox/groupId    artifactIdspringfox-swagger2/artifactId    version2.9.2/version/dependency dependency    groupIdio.springfox/groupId    artifactIdspringfox-swagger-ui/artifactId    version2.9.2/version/dependency

dependency
groupIdio.springfox/groupId
artifactIdspringfox-swagger2/artifactId
version2.9.2/version
/dependency

dependency
groupIdio.springfox/groupId
artifactIdspringfox-swagger-ui/artifactId
version2.9.2/version
/dependency

添加Swagger的配置

123456789101112131415161718192021
@Configurationpublic class Swagger2Config {     @Bean    public Docket createRestApi() {        return new Docket(DocumentationType.SWAGGER_2)                .apiInfo(apiInfo())                .select()                .apis(RequestHandlerSelectors.basePackage("XXX"))                .paths(PathSelectors.any())                .build();    }     private ApiInfo apiInfo() {        return new ApiInfoBuilder()                .title("XXX")                .description("经供参考")                .version("1.0")                .build();    }}

@Configuration
public class Swagger2Config {


@Bean
public Docket createRestApi() {
    return new Docket(DocumentationType.SWAGGER_2)
            .apiInfo(apiInfo())
            .select()
            .apis(RequestHandlerSelectors.basePackage("XXX"))
            .paths(PathSelectors.any())
            .build();
}

private ApiInfo apiInfo() {
    return new ApiInfoBuilder()
            .title("XXX")
            .description("经供参考")
            .version("1.0")
            .build();
}

}

总结

在整个过程中,遇到的坑就是在Spring boot中Filter的自动注入,中间考虑有不使用注入的方式解决,即直接使用 new JwtFilter()的方式,虽然也能解决问题,但是不是很完美,最终还是在网上找到解决方案。对Shiro的Filter链的执行过程加强了理解,能够使用自定的Filter解决实际问题。还有一个后续的问题,退出登录时的Jwt的token处理,它本身不能像Session一样,退出就清除,理论上只要没过期,就一直存在。可以考虑使用缓存,退出时清除即可,然后在校验时,先从缓存获取进行判断。

原文始发于:

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> Shrio使用Jwt达到前后端分离


 上一篇
请给SpringBoot多一些内存 请给SpringBoot多一些内存
点击上方“后端技术精选”,选择“置顶公众号” 技术文章第一时间送达! 作者:襄垣 juejin.im/post/5c89f266f265da2d8763b5f9 概述SprintBoot总体来说,搭建还是比较容易的,特别是S
下一篇 
SpringBoot整合MyBatis-Plus3.1详细教程 SpringBoot整合MyBatis-Plus3.1详细教程
点击上方“后端技术精选”,选择“置顶公众号” 技术文章第一时间送达! 作者:Sans_ juejin.im/post/5cfa6e465188254ee433bc69 一.说明Mybatis-Plus是一个Mybatis框架