Shiro同时支持Session和JWT Token两种认证方式

由于手机端不能存cookie,所以传统的session存储登录信息的登录方式(后面简称session登录)不能用,所以需要一个既支持session登录后访问有访问权限控制的url又支持无状态化token方式的认证。对于无状态话的token认证,目前比较流行的是JWT token。关于JWT Token的介绍请自行查阅网上资料。由于我们使用的Shiro认证授权框架,Shiro默认实现的是基于Session的认证和授权,为了实现同时支持Session和JWT Token两种认证方式,需要在了解Shiro认证授权框架的集成上 实现JWT token的访问控制逻辑。

1. 认证流程

针对用户需求和安全需求,需要实现以下几种场景的认证。

  • 基于浏览器的Session认证方式,需要实现多个web应用之间的SSO。
  • 移动端基于JWT Token的无状态认证,需要考虑token的足够安全和token的自动刷新(因为移动端不能因为token的过期,而中断应用导致用户体验差)
  • 由前端发起,后端微服务之间的调用,由于这种调用关系,微服务之间会进行session的共享,可以通过cookie来实现SSO
  • 来自于内部的一些服务,比如定时的Point service,由于它无Session,因此对于这种服务,系统会内置一个系统用户,再以JWT Token的方式进行认证


    Screen Shot 2021-08-03 at 3.41.22 PM.png

上面红色连接线表示基于JWT Token的Mobile App认证方式,蓝色连线表示基于Session的登录方式。其中内部定时器或者服务也是基于JWT Token认证方式,只是需要内置一些系统用户。

2. 实现步骤

2.1. Shiro默认访问步骤

场景一、访问登录请求

比如我们常见会定义一个/login的请求,接受用户名和密码参数(一般密码都会加盐hash)。对于这种请求,Shiro会执行以下的两步逻辑。

  • 在代码里会写到获取Shiro的Subject,创建一个token,通常是UsernamePasswordToken,将请求参数的账户密码填充进去,然后调用subject.login(token)
  • 接下来到支持处理这个token的realm中调用 realm doGetAuthenticationInfo 鉴权,鉴权后,session中就存有你的登录信息了

场景二、访问普通API

  • 到 Shiro 的 PathMatchingFilter preHandle 方法判断一个请求的访问权限是可以直接放行还是需要 Shiro 自己实现的AccessControlFilter 来处理访问请求
  • 假设到了 AccessControlFilter 实现类,首先在 isAccessAllowed 判断是否可以访问,如果可以则直接放行访问,如果不可以则到 onAccessDenied 方法处理,并继续调用 realm doGetAuthorizationInfo 授权判断是否有足够的权限来访问
  • 假设有足够的权限的话就访问到自己定义的 controller了

2.2. 支持JWT Token访问

Shiro默认支持的是Session认证方式,为了支持JWT Token认证方式,需要实现 AccessControlFilter 来修改控制访问的逻辑。需要完成的工作有以下方面:

要做的有下面几方面

  • [自定义实现AccessControlFilter (JWTAuthcFilter)]
  • S[hiro的过滤链上添加自定义的]
  • [自定义realm(JWTShiroRealm][),不用账户密码登录鉴权(UsernamePasswordToken),而使用自定义的token(JWTToken]
  • [自定义一个token(TokenRealm),存储参数和加密参数等]
  • 增加一个JWTTokenRefreshInterceptor来拦截请求,检测是否需要刷新token

2.3. 实现详情

具体见代码,分别是JWTAuthcFilter,JWTPrincipal,JWTTokenRefreshInterceptor,JWTWebMvcConfigurer,ShiroConfig,JWTToken等。

Screen Shot 2021-08-03 at 3.44.04 PM.png

2.3.1 JWTAuthcFilter

import com.google.common.base.Strings;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Slf4j
@AllArgsConstructor
public class JWTAuthcFilter extends AccessControlFilter {

    private final String headerKeyOfToken;

    private final JWTUserAuthService userAuthService;

    private final boolean isDisabled;


    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        if(isDisabled){
            log.info("Shiro Authentication is disabled, hence  can access api directly.");
            return true;
        }else{
            log.info("Shiro Authentication is enabled, to continue to execute onAccessDenied method");
        }

        return false;
    }

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
        // 登录状态判断
        log.info("onAccessDenied......");
        Subject subject = getSubject(request, response);
        if (subject.isAuthenticated()) {
            return true;
        }

        //从header或URL参数中查找token
        HttpServletRequest req = (HttpServletRequest) request;
        String authorization = req.getHeader(headerKeyOfToken);
        if (Strings.isNullOrEmpty(authorization)) {
            authorization = req.getParameter(headerKeyOfToken);
        }
        JWTToken token = new JWTToken(authorization);
        try {
            getSubject(request, response).login(token);
        } catch (Exception e) {
            log.error("认证失败:" + e.getMessage());
            this.userAuthService.onAuthenticationFailed((HttpServletRequest) request, (HttpServletResponse) response);
            return false;
        }
        return true;

    }
}

2.3.2 JWTPrincipal

import lombok.Data;


@Data
public class JWTPrincipal {

    private String account;

    private int userId;

    private long expiresAt;


}

2.3.3 JWTWebMvcConfigurer

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Slf4j
@Configuration
@ConditionalOnProperty(prefix = "shiro.jwt", name = "enable-auto-refresh-token", havingValue = "true")
public class JWTWebMvcConfigurer implements WebMvcConfigurer {

    @Autowired
    private ShiroConfig shiroConfig;

    @Autowired
    private JWTUserAuthService userAuthService;

    @Bean
    @ConditionalOnProperty(prefix = "shiro.jwt", name = "enable-auto-refresh-token", havingValue = "true")
    public JWTTokenRefreshInterceptor tokenRefreshInterceptor() {
        return new JWTTokenRefreshInterceptor(userAuthService, shiroConfig.getHeaderKeyOfToken(),
                shiroConfig.getMaxAliveMinute(), shiroConfig.getAccountAlias());
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        InterceptorRegistration reg = registry.addInterceptor(tokenRefreshInterceptor());
        String[] patterns = shiroConfig.getUrlPattern().split(",");
        log.info("启用token自动刷新机制,已注册TokenRefreshInterceptor");
        for (String urlPattern : patterns) {
            log.info("TokenRefreshInterceptor匹配URL规则:" + urlPattern);
            reg.addPathPatterns(urlPattern);
        }
    }

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        //允许访问header中的与token相关属性
        String[] urls = shiroConfig.getUrlPattern().split(",");
        for (String url : urls) {
            registry.addMapping(url).exposedHeaders(shiroConfig.getHeaderKeyOfToken());
        }
    }
}

2.3.4 ShiroConfig

import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.StrUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.pam.FirstSuccessfulStrategy;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
import org.apache.shiro.session.mgt.eis.SessionDAO;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.Cookie;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

@Configuration
@Slf4j
@Data
public class ShiroConfig {

    @Value("${shiro.session.timeout:1800000}")
    private Long sessionTimeout;

    @Value("${shiro.retry}")
    private Integer retryLimit;

    @Value("${shiro.lock}")
    private Integer lockLimit;

    @Value("${shiro.disabled:false}")
    private boolean isDisabled;

    @Value("${shiro.lock-duration}")
    private Long lockDuration;

    @Value("${spring.application.name}")
    private String name;

    @Value("${server.servlet.session.cookie.http-only:true}")
    private Boolean httpOnly;

    @Value("${server.servlet.session.cookie.secure:false}")
    private Boolean secure;

    @Value("${shiro.loginurl:/platform-user-service/login}")
    private String loginUrl;

    @Value("${shiro.overwrite.loginurl:}")
    private String overWriteLoginUrl;

    @Value("${shiro.jwt.urlPattern:/*}")
    private String  urlPattern;

    @Value("${shiro.jwt.maxAliveMinute:30}")
    private int maxAliveMinute;

    @Value("${shiro.jwt.maxIdleMinute:60}")
    private int maxIdleMinute;

    @Value("${shiro.jwt.headerKeyOfToken:access_token}")
    private String headerKeyOfToken;

    @Value("${shiro.jwt.accountAlias:account}")
    private String accountAlias;

    @Value("${shiro.jwt.enableAutoRefreshToken:false}")
    private boolean enableAutoRefreshToken;




    @Autowired
    private JWTUserAuthService userAuthService;



    @Bean
    public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        log.info("overwrite login url {}", overWriteLoginUrl);
        if(overWriteLoginUrl == null || overWriteLoginUrl.isEmpty()){
            shiroFilterFactoryBean.setLoginUrl(loginUrl);
        }else{
            shiroFilterFactoryBean.setLoginUrl(overWriteLoginUrl);
        }

        Map filters = new HashMap();
        filters.put(GlobalConstant.JWT_AUTHC, jwtAuthcFilter());
        shiroFilterFactoryBean.setFilters(filters);

        shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");
        Map filterChainDefinitionMap = new LinkedHashMap<>();
        filterChainDefinitionMap.put("/css/**", "anon");
        filterChainDefinitionMap.put("/img/**", "anon");
        filterChainDefinitionMap.put("/images/**", "anon");
        filterChainDefinitionMap.put("/js/**", "anon");
        filterChainDefinitionMap.put("/plugins/**", "anon");
        filterChainDefinitionMap.put("/login", "anon");
        filterChainDefinitionMap.put("/token", "anon");
        filterChainDefinitionMap.put("/api/v1.0/login", "anon");
        filterChainDefinitionMap.put("/api/v1.0/token", "anon");
        filterChainDefinitionMap.put("/api/v1.0/ping", "anon");
        filterChainDefinitionMap.put("/api/v1.0/message", "anon");
        filterChainDefinitionMap.put("/api/v1.0/user", GlobalConstant.JWT_AUTHC);
        filterChainDefinitionMap.put("/**", "authc");


        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }



    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();

        defaultWebSecurityManager.setAuthenticator(modularRealmAuthenticator());
        List realms = new ArrayList<>();
        realms.add(jwtShiroRealm());
        realms.add(shiroRealm());
        defaultWebSecurityManager.setRealms(realms);
        defaultWebSecurityManager.setSessionManager(getDefaultWebSessionManager());
        //defaultWebSecurityManager.setRememberMeManager(cookieRememberMeManager());
        defaultWebSecurityManager.setCacheManager(ehCacheManager());
        return defaultWebSecurityManager;
    }

    private DefaultWebSessionManager getDefaultWebSessionManager() {
        DefaultWebSessionManager defaultWebSessionManager = new DefaultWebSessionManager();
        defaultWebSessionManager.setGlobalSessionTimeout(sessionTimeout);
        defaultWebSessionManager.setSessionIdCookie(getSessionIdCookie());
        defaultWebSessionManager.setSessionIdCookieEnabled(true);
        defaultWebSessionManager.setCacheManager(ehCacheManager());
        defaultWebSessionManager.setSessionDAO(sessionDAO());

        return defaultWebSessionManager;
    }


    @Bean
    public EhCacheManager ehCacheManager() {
        EhCacheManager ehCacheManager = new EhCacheManager();
        ehCacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
        return ehCacheManager;
    }


    private SimpleCookie rememberMeCookie() {
        SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
        simpleCookie.setHttpOnly(true);
        simpleCookie.setMaxAge(2592000);
        return simpleCookie;
    }

    private SimpleCookie getSessionIdCookie() {
        SimpleCookie simpleCookie = new SimpleCookie(name);

        simpleCookie.setHttpOnly(httpOnly);
        simpleCookie.setMaxAge(1000 * 60);
        simpleCookie.setPath(StrUtil.SLASH);
        simpleCookie.setSameSite(Cookie.SameSiteOptions.LAX);
        simpleCookie.setSecure(secure);

        return simpleCookie;
    }
    /**
     * Remember my manager
     *
     * @author FastKing
     * @date 12:52 2018/9/28
     **/
    private CookieRememberMeManager cookieRememberMeManager() {
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        cookieRememberMeManager.setCookie(rememberMeCookie());
        cookieRememberMeManager.setCipherKey(Base64.decode("4AvVhmFLUs0KTA3Kprsdag=="));
        return cookieRememberMeManager;
    }




    @Bean
    public SessionDAO sessionDAO() {
        EnterpriseCacheSessionDAO cacheSessionDAO = new EnterpriseCacheSessionDAO();
        cacheSessionDAO.setActiveSessionsCacheName("shiro-activeSessionCache");
        return cacheSessionDAO;
    }


    @Bean
    public CredentialsMatcher retryLimitCredentialsMatcher() {
        return new RetryLimitCredentialsMatcher(retryLimit, lockLimit, lockDuration);
    }

    @Bean
    public JWTAuthcFilter jwtAuthcFilter() {
        return new JWTAuthcFilter(GlobalConstant.HEADER_KEY_TOKEN, userAuthService, isDisabled);
    }

    @Bean
    public ModularRealmAuthenticator modularRealmAuthenticator(){
        ModularRealmAuthenticator modularRealmAuthenticator=new ModularRealmAuthenticator();
        modularRealmAuthenticator.setAuthenticationStrategy(new FirstSuccessfulStrategy());
        return modularRealmAuthenticator;
    }

    @Bean
    public JWTShiroRealm jwtShiroRealm() {
        JWTShiroRealm tokenRealm = new JWTShiroRealm(userAuthService, accountAlias, maxIdleMinute);
        tokenRealm.setCachingEnabled(false);
        return tokenRealm;
    }



    @Bean
    public ShiroRealm shiroRealm() {
        ShiroRealm shiroRealm = new ShiroRealm();
        shiroRealm.setCredentialsMatcher(retryLimitCredentialsMatcher());
        return shiroRealm;
    }
}

2.3.5 JWTShiroRealm

import com.auth0.jwt.interfaces.DecodedJWT;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;


@AllArgsConstructor
@Slf4j
public class JWTShiroRealm extends AuthorizingRealm {

    private final JWTUserAuthService userAuthService;
    private final String accountAlias;
    private final int maxIdleMinute;


    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JWTToken;
    }


    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        JWTPrincipal principal = (JWTPrincipal) principals.getPrimaryPrincipal();
        SimpleAuthorizationInfo authInfo = new SimpleAuthorizationInfo();
        UserInfo up = userAuthService.getUserInfo(principal.getAccount());
        if (up != null && up.getPermissions() != null) {
            authInfo.addStringPermissions(up.getPermissions());
        }
        return authInfo;
    }


    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth){
        String token = (String) auth.getCredentials();
        String username = JWTHelper.getAccount(token, accountAlias);
        if (username == null) {
            throw new AuthenticationException("无效的请求");
        }
        UserInfo user = userAuthService.getUserInfo(username);
        if (user == null) {
            throw new AuthenticationException("未找到用户信息");
        }
        DecodedJWT jwt = JWTHelper.verify(token, user.getSecret(), maxIdleMinute);
        if (jwt == null) {
            throw new AuthenticationException("token已经过期,请重新登录");
        }
        JWTPrincipal principal = new JWTPrincipal();
        principal.setAccount(user.getAccount());
        principal.setUserId(user.getUserId());
        principal.setExpiresAt(jwt.getExpiresAt().getTime());
        //这里实际上会将AuthenticationToken.getCredentials()与传入的第二个参数credentials进行比较
        //第一个参数是登录成功后,可以通过subject.getPrincipal获取
        return new SimpleAuthenticationInfo(principal, token, this.getName());
    }
}

2.3.6 ShiroRealm

import cn.hutool.core.text.CharSequenceUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

import javax.annotation.Resource;
import java.util.Objects;
import java.util.Set;

@Slf4j
public class ShiroRealm extends AuthorizingRealm {

    @Resource
    private LoginService loginService;

    @Resource
    private RoleService roleService;

    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof UsernamePasswordToken;
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        UserInfoVO emsUserInfo = (UserInfoVO) principals.getPrimaryPrincipal();
        Set perms = roleService.selectPermsByRole(emsUserInfo.getRoleId());
        Set roles = roleService.selectRoleCodeByRole(emsUserInfo.getRoleId());
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        authorizationInfo.setStringPermissions(perms);
        authorizationInfo.setRoles(roles);
        return authorizationInfo;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) {
        String loginId = (String) authenticationToken.getPrincipal();
        UserInfoVO emsUserInfo = loginService.getEmsUserInfo(loginId);

        if (Objects.isNull(emsUserInfo)) {
            emsUserInfo = new UserInfoVO();
            emsUserInfo.setPassword(CharSequenceUtil.EMPTY);
        }
        return new SimpleAuthenticationInfo(emsUserInfo, emsUserInfo.getPassword(), this.getName());
    }

    @Override
    public boolean isPermitted(PrincipalCollection principals, String permission) {
        UserInfoVO emsUserInfo = (UserInfoVO) principals.getPrimaryPrincipal();
        if (Objects.isNull(emsUserInfo.getRoleMenuInfo())) {
            return false;
        }
        return emsUserInfo.getRoleMenuInfo().getIsAdmin() || super.isPermitted(principals, permission);
    }

    @Override
    public boolean hasRole(PrincipalCollection principals, String roleIdentifier) {
        UserInfoVO emsUserInfo = (UserInfoVO) principals.getPrimaryPrincipal();
        if (Objects.isNull(emsUserInfo.getRoleMenuInfo())) {
            return false;
        }
        return emsUserInfo.getRoleMenuInfo().getIsAdmin() || super.isPermitted(principals, roleIdentifier);
    }
}

2.4. 密码加密

为了兼容web端和移动端对密码的统一,在web端使用的是通过JavaScript和Web Crypto API来实现对数据进行端到端加密,因此移动端同样需要实现此加密算法。为了方便移动端的开发,使用Java封装了这套加密库,移动端可以直接调用。

2.5. JWT Token刷新

accessToken 的有效期由两个配置构成,maxAliveMinute 和 maxIdleMinute,配置见下面的配置章节。maxAliveMinute 定义了 accessToken 的理论过期时间,而 maxIdleMinute 定义了 accessToken 的最大生存周期。 在用户管理模块中增加了 HandlerInterceptor 用来处理 Token 的自动刷新问题,如果传入的 Token 已经超过 maxAliveMinute 设定的时间,但还没有达到 maxIdleMinute 的限制,则会自动刷新该用户的 accessToken 并添加在 response header,客户端如果在响应头中发现有新的 token 返回,说明当前 token 即将失效,需要及时更新自身存储的 token。这个机制实际是提供一个窗口期,让客户端安全的刷新 accessToken。

2.6. 系统配置

配置主要分为以下几个部分:

2.6.1. Shiro session配置

shiro:
  retry: 5 # 重试次数   lock: 5 # 锁定次数   lock-duration: 1 # 锁定时长 min   disabled: false
  session:
    timeout: 1800000
    loginurl: /login

2.6.2. Shiro JWT配置

shiro:
  retry: 5 # 重试次数
  lock: 5 # 锁定次数
  lock-duration: 1 # 锁定时长 min
  disabled: false # A&A开关
  session:
    timeout: 1800000
  loginurl: /login
  jwt:
    maxAliveMinute: 1 # jwt token过期时间,单位minutes
  maxIdleMinute: 120 # Jwt token最大存活时间,单位minutes
  headerKeyOfToken: access_token # Jwt token的header key name
  accountAlias: account # Jwt token account key name
  enableAutoRefreshToken: true # 是否自动刷新access token
  urlPattern: /api/v1.0/* # 需要刷新token的API Pattern

注意urlPattern,为了支持刷新token,定义了urlpattern,因此需要所有的服务都已api/v1.0作为前缀

2.7. 调用方式

2.7.1. web页面基于session访问

在web前端页面访问任一个API,都会跳转到登录页面,输入用户名和密码即可登录。

2.7.2. Mobile基于JWT Token访问

login

curl -X POST [http://localhost:50000/api/v1.0/token](http://localhost:50000/api/v1.0/token) -H "accept: application/json" -H "Content-Type: application/json" -d "{\"loginId\":\"admin\",\"password\":\"8SLGGbu7IYXVx4DJ.IGcMdlUQkaxDHG82fbCNCMC7LzWgex40qAFMnQ==\"}"

在access_token中返回jwt token如下:

login response

{
"code": 200,
"message": "操作成功",
"data": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2Mjc5Njg5MDUsImFjY291bnQiOiJhZG1pbiJ9.I5ToKyLKb22lxpo_LmA2mEHPXLMUUdmXm556LqRsHd0"
}

request api

curl -X POST [http://localhost:50000/api/v1.0/user](http://localhost:50000/api/v1.0/user) -H "accept: application/json" -H "Content-Type: application/json" -H "access_token:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2Mjc5Njg5MDUsImFjY291bnQiOiJhZG1pbiJ9.I5ToKyLKb22lxpo_LmA2mEHPXLMUUdmXm556LqRsHd0" -d "{\"userId\":8}"

写在最后

由于涉及到公司的一些业务代码,因此不方便保留在代码中,因此,上述代码不能编译成功,主要是如何实现多认证系统的一个思路,具体我也是参考下面的两篇文章来实现。

  • https://github.com/t4Wang/shirostateless
  • https://github.com/davidfantasy/shrio-with-jwt-spring-boot-starter/tree/master/src/main/java/com/github/davidfantasy/jwtshiro

你可能感兴趣的:(Shiro同时支持Session和JWT Token两种认证方式)