Shiro同時支持Session和JWT Token兩種認證方式

由于手機端不能存cookie,所以傳統(tǒng)的session存儲登錄信息的登錄方式(后面簡稱session登錄)不能用,所以需要一個既支持session登錄后訪問有訪問權(quán)限控制的url又支持無狀態(tài)化token方式的認證。對于無狀態(tài)話的token認證,目前比較流行的是JWT token。關(guān)于JWT Token的介紹請自行查閱網(wǎng)上資料。由于我們使用的Shiro認證授權(quán)框架,Shiro默認實現(xiàn)的是基于Session的認證和授權(quán),為了實現(xiàn)同時支持Session和JWT Token兩種認證方式,需要在了解Shiro認證授權(quán)框架的集成上 實現(xiàn)JWT token的訪問控制邏輯。

1. 認證流程

針對用戶需求和安全需求,需要實現(xiàn)以下幾種場景的認證。

  • 基于瀏覽器的Session認證方式,需要實現(xiàn)多個web應(yīng)用之間的SSO。
  • 移動端基于JWT Token的無狀態(tài)認證,需要考慮token的足夠安全和token的自動刷新(因為移動端不能因為token的過期,而中斷應(yīng)用導致用戶體驗差)
  • 由前端發(fā)起,后端微服務(wù)之間的調(diào)用,由于這種調(diào)用關(guān)系,微服務(wù)之間會進行session的共享,可以通過cookie來實現(xiàn)SSO
  • 來自于內(nèi)部的一些服務(wù),比如定時的Point service,由于它無Session,因此對于這種服務(wù),系統(tǒng)會內(nèi)置一個系統(tǒng)用戶,再以JWT Token的方式進行認證


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

上面紅色連接線表示基于JWT Token的Mobile App認證方式,藍色連線表示基于Session的登錄方式。其中內(nèi)部定時器或者服務(wù)也是基于JWT Token認證方式,只是需要內(nèi)置一些系統(tǒng)用戶。

2. 實現(xiàn)步驟

2.1. Shiro默認訪問步驟

場景一、訪問登錄請求

比如我們常見會定義一個/login的請求,接受用戶名和密碼參數(shù)(一般密碼都會加鹽hash)。對于這種請求,Shiro會執(zhí)行以下的兩步邏輯。

  • 在代碼里會寫到獲取Shiro的Subject,創(chuàng)建一個token,通常是UsernamePasswordToken,將請求參數(shù)的賬戶密碼填充進去,然后調(diào)用subject.login(token)
  • 接下來到支持處理這個token的realm中調(diào)用 realm doGetAuthenticationInfo 鑒權(quán),鑒權(quán)后,session中就存有你的登錄信息了

場景二、訪問普通API

  • 到 Shiro 的 PathMatchingFilter preHandle 方法判斷一個請求的訪問權(quán)限是可以直接放行還是需要 Shiro 自己實現(xiàn)的AccessControlFilter 來處理訪問請求
  • 假設(shè)到了 AccessControlFilter 實現(xiàn)類,首先在 isAccessAllowed 判斷是否可以訪問,如果可以則直接放行訪問,如果不可以則到 onAccessDenied 方法處理,并繼續(xù)調(diào)用 realm doGetAuthorizationInfo 授權(quán)判斷是否有足夠的權(quán)限來訪問
  • 假設(shè)有足夠的權(quán)限的話就訪問到自己定義的 controller了

2.2. 支持JWT Token訪問

Shiro默認支持的是Session認證方式,為了支持JWT Token認證方式,需要實現(xiàn) AccessControlFilter 來修改控制訪問的邏輯。需要完成的工作有以下方面:

要做的有下面幾方面

  • [自定義實現(xiàn)AccessControlFilter (JWTAuthcFilter)]
  • S[hiro的過濾鏈上添加自定義的]
  • [自定義realm(JWTShiroRealm][),不用賬戶密碼登錄鑒權(quán)(UsernamePasswordToken),而使用自定義的token(JWTToken]
  • [自定義一個token(TokenRealm),存儲參數(shù)和加密參數(shù)等]
  • 增加一個JWTTokenRefreshInterceptor來攔截請求,檢測是否需要刷新token

2.3. 實現(xiàn)詳情

具體見代碼,分別是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 {
        // 登錄狀態(tài)判斷
        log.info("onAccessDenied......");
        Subject subject = getSubject(request, response);
        if (subject.isAuthenticated()) {
            return true;
        }

        //從header或URL參數(shù)中查找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規(guī)則:" + urlPattern);
            reg.addPathPatterns(urlPattern);
        }
    }

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        //允許訪問header中的與token相關(guān)屬性
        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<String, Filter> filters = new HashMap();
        filters.put(GlobalConstant.JWT_AUTHC, jwtAuthcFilter());
        shiroFilterFactoryBean.setFilters(filters);

        shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");
        Map<String, String> 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<Realm> 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已經(jīng)過期,請重新登錄");
        }
        JWTPrincipal principal = new JWTPrincipal();
        principal.setAccount(user.getAccount());
        principal.setUserId(user.getUserId());
        principal.setExpiresAt(jwt.getExpiresAt().getTime());
        //這里實際上會將AuthenticationToken.getCredentials()與傳入的第二個參數(shù)credentials進行比較
        //第一個參數(shù)是登錄成功后,可以通過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<String> perms = roleService.selectPermsByRole(emsUserInfo.getRoleId());
        Set<String> 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端和移動端對密碼的統(tǒng)一,在web端使用的是通過JavaScript和Web Crypto API來實現(xiàn)對數(shù)據(jù)進行端到端加密,因此移動端同樣需要實現(xiàn)此加密算法。為了方便移動端的開發(fā),使用Java封裝了這套加密庫,移動端可以直接調(diào)用。

2.5. JWT Token刷新

accessToken 的有效期由兩個配置構(gòu)成,maxAliveMinute 和 maxIdleMinute,配置見下面的配置章節(jié)。maxAliveMinute 定義了 accessToken 的理論過期時間,而 maxIdleMinute 定義了 accessToken 的最大生存周期。 在用戶管理模塊中增加了 HandlerInterceptor 用來處理 Token 的自動刷新問題,如果傳入的 Token 已經(jīng)超過 maxAliveMinute 設(shè)定的時間,但還沒有達到 maxIdleMinute 的限制,則會自動刷新該用戶的 accessToken 并添加在 response header,客戶端如果在響應(yīng)頭中發(fā)現(xiàn)有新的 token 返回,說明當前 token 即將失效,需要及時更新自身存儲的 token。這個機制實際是提供一個窗口期,讓客戶端安全的刷新 accessToken。

2.6. 系統(tǒng)配置

配置主要分為以下幾個部分:

2.6.1. Shiro session配置

shiro:
  retry: 5 # 重試次數(shù)   lock: 5 # 鎖定次數(shù)   lock-duration: 1 # 鎖定時長 min   disabled: false
  session:
    timeout: 1800000
    loginurl: /login

2.6.2. Shiro JWT配置

shiro:
  retry: 5 # 重試次數(shù)
  lock: 5 # 鎖定次數(shù)
  lock-duration: 1 # 鎖定時長 min
  disabled: false # A&A開關(guān)
  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,因此需要所有的服務(wù)都已api/v1.0作為前綴

2.7. 調(diào)用方式

2.7.1. web頁面基于session訪問

在web前端頁面訪問任一個API,都會跳轉(zhuǎn)到登錄頁面,輸入用戶名和密碼即可登錄。

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}"

寫在最后

由于涉及到公司的一些業(yè)務(wù)代碼,因此不方便保留在代碼中,因此,上述代碼不能編譯成功,主要是如何實現(xiàn)多認證系統(tǒng)的一個思路,具體我也是參考下面的兩篇文章來實現(xiàn)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容