准备内容

简单的shiro无状态认证

  无状态认证拦截器

  1. import com.hjzgg.stateless.shiroSimpleWeb.Constants;
  2. import com.hjzgg.stateless.shiroSimpleWeb.realm.StatelessToken;
  3. import org.apache.shiro.web.filter.AccessControlFilter;
  4.  
  5. import javax.servlet.ServletRequest;
  6. import javax.servlet.ServletResponse;
  7. import javax.servlet.http.HttpServletResponse;
  8. import java.io.IOException;
  9. import java.util.HashMap;
  10. import java.util.Map;
  11.  
  12. /**
  13.  
  14. * <p>Version: 1.0
  15. */
  16. public class StatelessAuthcFilter extends AccessControlFilter {
  17.  
  18. @Override
  19. protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
  20. return false;
  21. }
  22.  
  23. @Override
  24. protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
  25. //1、客户端生成的消息摘要
  26. String clientDigest = request.getParameter(Constants.PARAM_DIGEST);
  27. //2、客户端传入的用户身份
  28. String username = request.getParameter(Constants.PARAM_USERNAME);
  29. //3、客户端请求的参数列表
  30. Map<String, String[]> params = new HashMap<String, String[]>(request.getParameterMap());
  31. params.remove(Constants.PARAM_DIGEST);
  32.  
  33. //4、生成无状态Token
  34. StatelessToken token = new StatelessToken(username, params, clientDigest);
  35.  
  36. try {
  37. //5、委托给Realm进行登录
  38. getSubject(request, response).login(token);
  39. } catch (Exception e) {
  40. e.printStackTrace();
  41. onLoginFail(response); //6、登录失败
  42. return false;
  43. }
  44. return true;
  45. }
  46.  
  47. //登录失败时默认返回401状态码
  48. private void onLoginFail(ServletResponse response) throws IOException {
  49. HttpServletResponse httpResponse = (HttpServletResponse) response;
  50. httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
  51. httpResponse.getWriter().write("login error");
  52. }
  53. }

  Subject工厂

  1. import org.apache.shiro.subject.Subject;
  2. import org.apache.shiro.subject.SubjectContext;
  3. import org.apache.shiro.web.mgt.DefaultWebSubjectFactory;
  4.  
  5. /**
  6.  
  7. * <p>Version: 1.0
  8. */
  9. public class StatelessDefaultSubjectFactory extends DefaultWebSubjectFactory {
  10.  
  11. @Override
  12. public Subject createSubject(SubjectContext context) {
  13. //不创建session
  14. context.setSessionCreationEnabled(false);
  15. return super.createSubject(context);
  16. }
  17. }

  注意,这里禁用了session

  无状态Realm

  1. import com.hjzgg.stateless.shiroSimpleWeb.codec.HmacSHA256Utils;
  2. import org.apache.shiro.authc.AuthenticationException;
  3. import org.apache.shiro.authc.AuthenticationInfo;
  4. import org.apache.shiro.authc.AuthenticationToken;
  5. import org.apache.shiro.authc.SimpleAuthenticationInfo;
  6. import org.apache.shiro.authz.AuthorizationInfo;
  7. import org.apache.shiro.authz.SimpleAuthorizationInfo;
  8. import org.apache.shiro.realm.AuthorizingRealm;
  9. import org.apache.shiro.subject.PrincipalCollection;
  10.  
  11. /**
  12.  
  13. * <p>Version: 1.0
  14. */
  15. public class StatelessRealm extends AuthorizingRealm {
  16. @Override
  17. public boolean supports(AuthenticationToken token) {
  18. //仅支持StatelessToken类型的Token
  19. return token instanceof StatelessToken;
  20. }
  21. @Override
  22. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
  23. //根据用户名查找角色,请根据需求实现
  24. String username = (String) principals.getPrimaryPrincipal();
  25. SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
  26. authorizationInfo.addRole("admin");
  27. return authorizationInfo;
  28. }
  29. @Override
  30. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
  31. StatelessToken statelessToken = (StatelessToken) token;
  32. String username = statelessToken.getUsername();
  33. String key = getKey(username);//根据用户名获取密钥(和客户端的一样)
  34. //在服务器端生成客户端参数消息摘要
  35. String serverDigest = HmacSHA256Utils.digest(key, statelessToken.getParams());
  36. System.out.println(statelessToken.getClientDigest());
  37. System.out.println(serverDigest);
  38. //然后进行客户端消息摘要和服务器端消息摘要的匹配
  39. return new SimpleAuthenticationInfo(
  40. username,
  41. serverDigest,
  42. getName());
  43. }
  44.  
  45. private String getKey(String username) {//得到密钥,此处硬编码一个
  46. if("admin".equals(username)) {
  47. return "dadadswdewq2ewdwqdwadsadasd";
  48. }
  49. return null;
  50. }
  51. }

  无状态Token

  1. import org.apache.shiro.authc.AuthenticationToken;
  2. import org.springframework.beans.*;
  3. import org.springframework.validation.DataBinder;
  4.  
  5. import java.util.HashMap;
  6. import java.util.Map;
  7.  
  8. /**
  9.  
  10. * <p>Version: 1.0
  11. */
  12. public class StatelessToken implements AuthenticationToken {
  13.  
  14. private String username;
  15. private Map<String, ?> params;
  16. private String clientDigest;
  17.  
  18. public StatelessToken(String username, Map<String, ?> params, String clientDigest) {
  19. this.username = username;
  20. this.params = params;
  21. this.clientDigest = clientDigest;
  22. }
  23.  
  24. public String getUsername() {
  25. return username;
  26. }
  27.  
  28. public void setUsername(String username) {
  29. this.username = username;
  30. }
  31.  
  32. public Map<String, ?> getParams() {
  33. return params;
  34. }
  35.  
  36. public void setParams( Map<String, ?> params) {
  37. this.params = params;
  38. }
  39.  
  40. public String getClientDigest() {
  41. return clientDigest;
  42. }
  43.  
  44. public void setClientDigest(String clientDigest) {
  45. this.clientDigest = clientDigest;
  46. }
  47.  
  48. @Override
  49. public Object getPrincipal() {
  50. return username;
  51. }
  52.  
  53. @Override
  54. public Object getCredentials() {
  55. return clientDigest;
  56. }
  57.  
  58. public static void main(String[] args) {
  59.  
  60. }
  61. public static void test1() {
  62. StatelessToken token = new StatelessToken(null, null, null);
  63. BeanWrapperImpl beanWrapper = new BeanWrapperImpl(token);
  64. beanWrapper.setPropertyValue(new PropertyValue("username", "hjzgg"));
  65. System.out.println(token.getUsername());
  66. }
  67.  
  68. public static void test2() {
  69. StatelessToken token = new StatelessToken(null, null, null);
  70. DataBinder dataBinder = new DataBinder(token);
  71. Map<String, Object> params = new HashMap<>();
  72. params.put("username", "hjzgg");
  73. PropertyValues propertyValues = new MutablePropertyValues(params);
  74. dataBinder.bind(propertyValues);
  75. System.out.println(token.getUsername());
  76. }
  77. }

  shiro配置文件

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:util="http://www.springframework.org/schema/util"
  4. xmlns:aop="http://www.springframework.org/schema/aop"
  5. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  6. xsi:schemaLocation="
  7. http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
  8. http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
  9. http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
  10.  
  11. <!-- Realm实现 -->
  12. <bean id="statelessRealm" class="com.hjzgg.stateless.shiroSimpleWeb.realm.StatelessRealm">
  13. <property name="cachingEnabled" value="false"/>
  14. </bean>
  15.  
  16. <!-- Subject工厂 -->
  17. <bean id="subjectFactory" class="com.hjzgg.stateless.shiroSimpleWeb.mgt.StatelessDefaultSubjectFactory"/>
  18.  
  19. <!-- 会话管理器 -->
  20. <bean id="sessionManager" class="org.apache.shiro.session.mgt.DefaultSessionManager">
  21. <property name="sessionValidationSchedulerEnabled" value="false"/>
  22. </bean>
  23.  
  24. <!-- 安全管理器 -->
  25. <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
  26. <property name="realm" ref="statelessRealm"/>
  27. <property name="subjectDAO.sessionStorageEvaluator.sessionStorageEnabled" value="false"/>
  28. <property name="subjectFactory" ref="subjectFactory"/>
  29. <property name="sessionManager" ref="sessionManager"/>
  30. </bean>
  31.  
  32. <!-- 相当于调用SecurityUtils.setSecurityManager(securityManager) -->
  33. <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
  34. <property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager"/>
  35. <property name="arguments" ref="securityManager"/>
  36. </bean>
  37.  
  38. <bean id="statelessAuthcFilter" class="com.hjzgg.stateless.shiroSimpleWeb.filter.StatelessAuthcFilter"/>
  39.  
  40. <!-- Shiro的Web过滤器 -->
  41. <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
  42. <property name="securityManager" ref="securityManager"/>
  43. <property name="filters">
  44. <util:map>
  45. <entry key="statelessAuthc" value-ref="statelessAuthcFilter"/>
  46. </util:map>
  47. </property>
  48. <property name="filterChainDefinitions">
  49. <value>
  50. /**=statelessAuthc
  51. </value>
  52. </property>
  53. </bean>
  54.  
  55. <!-- Shiro生命周期处理器-->
  56. <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
  57.  
  58. </beans>

  这里禁用了回话调度器的session存储

  web.xml配置

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <web-app
  3. xmlns="http://java.sun.com/xml/ns/javaee"
  4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  5. xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
  6. version="3.0"
  7. metadata-complete="false">
  8.  
  9. <display-name>shiro-example-chapter20</display-name>
  10.  
  11. <!-- Spring配置文件开始 -->
  12. <context-param>
  13. <param-name>contextConfigLocation</param-name>
  14. <param-value>
  15. classpath:spring-config-shiro.xml
  16. </param-value>
  17. </context-param>
  18. <listener>
  19. <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  20. </listener>
  21. <!-- Spring配置文件结束 -->
  22.  
  23. <!-- shiro 安全过滤器 -->
  24. <filter>
  25. <filter-name>shiroFilter</filter-name>
  26. <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
  27. <async-supported>true</async-supported>
  28. <init-param>
  29. <param-name>targetFilterLifecycle</param-name>
  30. <param-value>true</param-value>
  31. </init-param>
  32. </filter>
  33.  
  34. <filter-mapping>
  35. <filter-name>shiroFilter</filter-name>
  36. <url-pattern>/*</url-pattern>
  37. <dispatcher>REQUEST</dispatcher>
  38. </filter-mapping>
  39.  
  40. <servlet>
  41. <servlet-name>spring</servlet-name>
  42. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  43. <init-param>
  44. <param-name>contextConfigLocation</param-name>
  45. <param-value>classpath:spring-mvc.xml</param-value>
  46. </init-param>
  47. <load-on-startup>1</load-on-startup>
  48. <async-supported>true</async-supported>
  49. </servlet>
  50. <servlet-mapping>
  51. <servlet-name>spring</servlet-name>
  52. <url-pattern>/</url-pattern>
  53. </servlet-mapping>
  54.  
  55. </web-app>

  token生成工具类

  1. import org.apache.commons.codec.binary.Hex;
  2.  
  3. import javax.crypto.Mac;
  4. import javax.crypto.SecretKey;
  5. import javax.crypto.spec.SecretKeySpec;
  6. import java.util.List;
  7. import java.util.Map;
  8.  
  9. /**
  10.  
  11. * <p>Version: 1.0
  12. */
  13. public class HmacSHA256Utils {
  14.  
  15. public static String digest(String key, String content) {
  16. try {
  17. Mac mac = Mac.getInstance("HmacSHA256");
  18. byte[] secretByte = key.getBytes("utf-8");
  19. byte[] dataBytes = content.getBytes("utf-8");
  20.  
  21. SecretKey secret = new SecretKeySpec(secretByte, "HMACSHA256");
  22. mac.init(secret);
  23.  
  24. byte[] doFinal = mac.doFinal(dataBytes);
  25. byte[] hexB = new Hex().encode(doFinal);
  26. return new String(hexB, "utf-8");
  27. } catch (Exception e) {
  28. throw new RuntimeException(e);
  29. }
  30. }
  31.  
  32. public static String digest(String key, Map<String, ?> map) {
  33. StringBuilder s = new StringBuilder();
  34. for(Object values : map.values()) {
  35. if(values instanceof String[]) {
  36. for(String value : (String[])values) {
  37. s.append(value);
  38. }
  39. } else if(values instanceof List) {
  40. for(String value : (List<String>)values) {
  41. s.append(value);
  42. }
  43. } else {
  44. s.append(values);
  45. }
  46. }
  47. return digest(key, s.toString());
  48. }
  49.  
  50. }

  简单测试一下

  1. import com.alibaba.fastjson.JSONObject;
  2. import com.hjzgg.stateless.shiroSimpleWeb.codec.HmacSHA256Utils;
  3. import com.hjzgg.stateless.shiroSimpleWeb.utils.RestTemplateUtils;
  4. import org.junit.Test;
  5. import org.springframework.util.LinkedMultiValueMap;
  6. import org.springframework.util.MultiValueMap;
  7. import org.springframework.web.util.UriComponentsBuilder;
  8.  
  9. /**
  10. * <p>Version: 1.0
  11. */
  12. public class ClientTest {
  13.  
  14. private static final String WEB_URL = "http://localhost:8080/shiro/hello";
  15.  
  16. @Test
  17. public void testServiceHelloSuccess() {
  18. String username = "admin";
  19. String param11 = "param11";
  20. String param12 = "param12";
  21. String param2 = "param2";
  22. String key = "dadadswdewq2ewdwqdwadsadasd";
  23. JSONObject params = new JSONObject();
  24. params.put(Constants.PARAM_USERNAME, username);
  25. params.put("param1", param11);
  26. params.put("param1", param12);
  27. params.put("param2", param2);
  28. params.put(Constants.PARAM_DIGEST, HmacSHA256Utils.digest(key, params));
  29.  
  30. String result = RestTemplateUtils.get(WEB_URL, params);
  31. System.out.println(result);
  32. }
  33.  
  34. @Test
  35. public void testServiceHelloFail() {
  36. String username = "admin";
  37. String param11 = "param11";
  38. String param12 = "param12";
  39. String param2 = "param2";
  40. String key = "dadadswdewq2ewdwqdwadsadasd";
  41. MultiValueMap<String, String> params = new LinkedMultiValueMap<String, String>();
  42. params.add(Constants.PARAM_USERNAME, username);
  43. params.add("param1", param11);
  44. params.add("param1", param12);
  45. params.add("param2", param2);
  46. params.add(Constants.PARAM_DIGEST, HmacSHA256Utils.digest(key, params));
  47. params.set("param2", param2 + "1");
  48.  
  49. String url = UriComponentsBuilder
  50. .fromHttpUrl("http://localhost:8080/hello")
  51. .queryParams(params).build().toUriString();
  52. }
  53. }

  补充Spring中多重属性赋值处理

  以上参考 开涛老师的博文

相对复杂一点的shiro无状态认证

  *加入session,放入redis中(user_name作为key值,token作为hash值,当前登录时间作为value值)

  *用户登录互斥操作:如果互斥,清除redis中该用户对应的状态,重新写入新的状态;如果不互斥,写入新的状态,刷新key值,并检测该用户其他的状态是否已经超时(根据key值获取到所有的 key和hashKey的组合,判断value[登入时间]+timeout[超时时间] >= curtime[当前时间]),如果超时则清除状态。

  *使用esapi进行token的生成

  *认证信息,如果是web端则从cookie中获取,ajax从header中获取;如果是移动端也是从header中获取

  session manager逻辑

  1. import com.hjzgg.stateless.auth.token.ITokenProcessor;
  2. import com.hjzgg.stateless.auth.token.TokenFactory;
  3. import com.hjzgg.stateless.auth.token.TokenGenerator;
  4. import com.hjzgg.stateless.common.cache.RedisCacheTemplate;
  5. import com.hjzgg.stateless.common.esapi.EncryptException;
  6. import org.apache.commons.lang3.StringUtils;
  7. import org.slf4j.Logger;
  8. import org.slf4j.LoggerFactory;
  9. import org.springframework.beans.factory.annotation.Autowired;
  10. import org.springframework.beans.factory.annotation.Value;
  11. import org.springframework.stereotype.Component;
  12. @Component
  13. public class ShiroSessionManager {
  14. @Autowired
  15. private RedisCacheTemplate redisCacheTemplate;
  16. @Value("${sessionMutex}")
  17. private boolean sessionMutex = false;
  18. public static final String TOKEN_SEED = "token_seed";
  19. public static final String DEFAULT_CHARSET = "UTF-8";
  20. private final Logger logger = LoggerFactory.getLogger(getClass());
  21. private static String localSeedValue = null;
  22. /**
  23. * 获得当前系统的 token seed
  24. */
  25. public String findSeed() throws EncryptException {
  26. if(localSeedValue != null){
  27. return localSeedValue;
  28. } else {
  29. String seed = getSeedValue(TOKEN_SEED);
  30. if (StringUtils.isBlank(seed)) {
  31. seed = TokenGenerator.genSeed();
  32. localSeedValue = seed;
  33. redisCacheTemplate.put(TOKEN_SEED, seed);
  34. }
  35. return seed;
  36. }
  37. }
  38. public String getSeedValue(String key) {
  39. return (String) redisCacheTemplate.get(key);
  40. }
  41. /**
  42. * 删除session缓存
  43. *
  44. * @param sid mock的sessionid
  45. */
  46. public void removeSessionCache(String sid) {
  47. redisCacheTemplate.delete(sid);
  48. }
  49. private int getTimeout(String sid){
  50. return TokenFactory.getTokenInfo(sid).getIntegerExpr();
  51. }
  52. private String getCurrentTimeSeconds() {
  53. return String.valueOf(System.currentTimeMillis()/1000);
  54. }
  55. public void registOnlineSession(final String userName, final String token, final ITokenProcessor processor) {
  56. final String key = userName;
  57. logger.debug("token processor id is {}, key is {}, sessionMutex is {}!" , processor.getId(), key, sessionMutex);
  58. // 是否互斥,如果是,则踢掉所有当前用户的session,重新创建,此变量将来从配置文件读取
  59. if(sessionMutex){
  60. deleteUserSession(key);
  61. } else {
  62. // 清理此用户过期的session,过期的常为异常或者直接关闭浏览器,没有走正常注销的key
  63. clearOnlineSession(key);
  64. }
  65. redisCacheTemplate.hPut(userName, token, getCurrentTimeSeconds());
  66. int timeout = getTimeout(token);
  67. if (timeout > 0) {
  68. redisCacheTemplate.expire(token, timeout);
  69. }
  70. }
  71. private void clearOnlineSession(final String key) {
  72. redisCacheTemplate.hKeys(key).forEach((obj) -> {
  73. String hashKey = (String) obj;
  74. int timeout = getTimeout(hashKey);
  75. if (timeout > 0) {
  76. int oldTimeSecondsValue = Integer.valueOf((String) redisCacheTemplate.hGet(key, hashKey));
  77. int curTimeSecondsValue = (int) (System.currentTimeMillis()/1000);
  78. //如果 key-hashKey 对应的时间+过期时间 小于 当前时间,则剔除
  79. if(curTimeSecondsValue - (oldTimeSecondsValue+timeout) > 0) {
  80. redisCacheTemplate.hDel(key, hashKey);
  81. }
  82. }
  83. });
  84. }
  85. public boolean validateOnlineSession(final String key, final String hashKey) {
  86. int timeout = getTimeout(hashKey);
  87. if (timeout > 0) {
  88. String oldTimeSecondsValue = (String) redisCacheTemplate.hGet(key, hashKey);
  89. if (StringUtils.isEmpty(oldTimeSecondsValue)) {
  90. return false;
  91. } else {
  92. int curTimeSecondsValue = (int) (System.currentTimeMillis()/1000);
  93. if(Integer.valueOf(oldTimeSecondsValue)+timeout >= curTimeSecondsValue) {
  94. //刷新 key
  95. redisCacheTemplate.hPut(key, hashKey, getCurrentTimeSeconds());
  96. redisCacheTemplate.expire(key, timeout);
  97. return true;
  98. } else {
  99. redisCacheTemplate.hDel(key, hashKey);
  100. return false;
  101. }
  102. }
  103. } else {
  104. return redisCacheTemplate.hGet(key, hashKey) != null;
  105. }
  106. }
  107. // 注销用户时候需要调用
  108. public void delOnlineSession(final String key, final String hashKey){
  109. redisCacheTemplate.hDel(key, hashKey);
  110. }
  111. // 禁用或者删除用户时候调用
  112. public void deleteUserSession(final String key){
  113. redisCacheTemplate.delete(key);
  114. }
  115. }

  无状态认证过滤器

  1. package com.hjzgg.stateless.auth.shiro;
  2.  
  3. import com.alibaba.fastjson.JSONObject;
  4. import com.hjzgg.stateless.auth.token.ITokenProcessor;
  5. import com.hjzgg.stateless.auth.token.TokenFactory;
  6. import com.hjzgg.stateless.auth.token.TokenParameter;
  7. import com.hjzgg.stateless.common.constants.AuthConstants;
  8. import com.hjzgg.stateless.common.utils.CookieUtil;
  9. import com.hjzgg.stateless.common.utils.InvocationInfoProxy;
  10. import com.hjzgg.stateless.common.utils.MapToStringUtil;
  11. import org.apache.commons.codec.binary.Base64;
  12. import org.apache.commons.lang3.StringUtils;
  13. import org.apache.shiro.SecurityUtils;
  14. import org.apache.shiro.authc.AuthenticationException;
  15. import org.apache.shiro.subject.Subject;
  16. import org.apache.shiro.web.filter.AccessControlFilter;
  17. import org.apache.shiro.web.util.WebUtils;
  18. import org.slf4j.Logger;
  19. import org.slf4j.LoggerFactory;
  20. import org.slf4j.MDC;
  21. import org.springframework.beans.factory.annotation.Autowired;
  22. import org.springframework.beans.factory.annotation.Value;
  23.  
  24. import javax.servlet.ServletRequest;
  25. import javax.servlet.ServletResponse;
  26. import javax.servlet.http.Cookie;
  27. import javax.servlet.http.HttpServletRequest;
  28. import javax.servlet.http.HttpServletResponse;
  29. import java.io.IOException;
  30. import java.lang.reflect.Field;
  31. import java.lang.reflect.Modifier;
  32. import java.net.URL;
  33. import java.util.*;
  34.  
  35. public class StatelessAuthcFilter extends AccessControlFilter {
  36.  
  37. private static final Logger log = LoggerFactory.getLogger(StatelessAuthcFilter.class);
  38.  
  39. public static final int HTTP_STATUS_AUTH = 306;
  40.  
  41. @Value("${filterExclude}")
  42. private String exeludeStr;
  43.  
  44. @Autowired
  45. private TokenFactory tokenFactory;
  46.  
  47. private String[] esc = new String[] {
  48. "/logout","/login","/formLogin",".jpg",".png",".gif",".css",".js",".jpeg"
  49. };
  50.  
  51. private List<String> excludCongtextKeys = new ArrayList<>();
  52.  
  53. public void setTokenFactory(TokenFactory tokenFactory) {
  54. this.tokenFactory = tokenFactory;
  55. }
  56.  
  57. public void setEsc(String[] esc) {
  58. this.esc = esc;
  59. }
  60.  
  61. public void setExcludCongtextKeys(List<String> excludCongtextKeys) {
  62. this.excludCongtextKeys = excludCongtextKeys;
  63. }
  64.  
  65. @Override
  66. protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
  67. return false;
  68. }
  69.  
  70. @Override
  71. protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
  72.  
  73. boolean isAjax = isAjax(request);
  74.  
  75. // 1、客户端发送来的摘要
  76. HttpServletRequest hReq = (HttpServletRequest) request;
  77. HttpServletRequest httpRequest = hReq;
  78. Cookie[] cookies = httpRequest.getCookies();
  79. String authority = httpRequest.getHeader("Authority");
  80.  
  81. //如果header中包含,则以header为主,否则,以cookie为主
  82. if(StringUtils.isNotBlank(authority)){
  83. Set<Cookie> cookieSet = new HashSet<Cookie>();
  84. String[] ac = authority.split(";");
  85. for(String s : ac){
  86. String[] cookieArr = s.split("=");
  87. String key = StringUtils.trim(cookieArr[0]);
  88. String value = StringUtils.trim(cookieArr[1]);
  89. Cookie cookie = new Cookie(key, value);
  90. cookieSet.add(cookie);
  91. }
  92. cookies = cookieSet.toArray(new Cookie[]{});
  93. }
  94.  
  95. String tokenStr = CookieUtil.findCookieValue(cookies, AuthConstants.PARAM_TOKEN);
  96. String cookieUserName = CookieUtil.findCookieValue(cookies, AuthConstants.PARAM_USERNAME);
  97.  
  98. String loginTs = CookieUtil.findCookieValue(cookies, AuthConstants.PARAM_LOGINTS);
  99.  
  100. // 2、客户端传入的用户身份
  101. String userName = request.getParameter(AuthConstants.PARAM_USERNAME);
  102. if (userName == null && StringUtils.isNotBlank(cookieUserName)) {
  103. userName = cookieUserName;
  104. }
  105.  
  106. boolean needCheck = !include(hReq);
  107.  
  108. if (needCheck) {
  109. if (StringUtils.isEmpty(tokenStr) || StringUtils.isEmpty(userName)) {
  110. if (isAjax) {
  111. onAjaxAuthFail(request, response);
  112. } else {
  113. onLoginFail(request, response);
  114. }
  115. return false;
  116. }
  117.  
  118. // 3、客户端请求的参数列表
  119. Map<String, String[]> params = new HashMap<String, String[]>(request.getParameterMap());
  120.  
  121. ITokenProcessor tokenProcessor = tokenFactory.getTokenProcessor(tokenStr);
  122. TokenParameter tp = tokenProcessor.getTokenParameterFromCookie(cookies);
  123. // 4、生成无状态Token
  124. StatelessToken token = new StatelessToken(userName, tokenProcessor, tp, params, new String(tokenStr));
  125.  
  126. try {
  127. // 5、委托给Realm进行登录
  128. getSubject(request, response).login(token); // 这个地方应该验证上下文信息中的正确性
  129.  
  130. // 设置上下文变量
  131. InvocationInfoProxy.setUserName(userName);
  132. InvocationInfoProxy.setLoginTs(loginTs);
  133. InvocationInfoProxy.setToken(tokenStr);
  134.  
  135. //设置上下文携带的额外属性
  136. initExtendParams(cookies);
  137.  
  138. initMDC();
  139. afterValidate(hReq);
  140. } catch (Exception e) {
  141. log.error(e.getMessage(), e);
  142. if (isAjax && e instanceof AuthenticationException) {
  143. onAjaxAuthFail(request, response); // 6、验证失败,返回ajax调用方信息
  144. return false;
  145. } else {
  146. onLoginFail(request, response); // 6、登录失败,跳转到登录页
  147. return false;
  148. }
  149. }
  150. return true;
  151. } else {
  152. return true;
  153. }
  154.  
  155. }
  156.  
  157. private boolean isAjax(ServletRequest request) {
  158. boolean isAjax = false;
  159. if (request instanceof HttpServletRequest) {
  160. HttpServletRequest rq = (HttpServletRequest) request;
  161. String requestType = rq.getHeader("X-Requested-With");
  162. if (requestType != null && "XMLHttpRequest".equals(requestType)) {
  163. isAjax = true;
  164. }
  165. }
  166. return isAjax;
  167. }
  168.  
  169. protected void onAjaxAuthFail(ServletRequest request, ServletResponse resp) throws IOException {
  170. HttpServletResponse response = (HttpServletResponse) resp;
  171. JSONObject json = new JSONObject();
  172. json.put("msg", "auth check error!");
  173. response.setStatus(HTTP_STATUS_AUTH);
  174. response.getWriter().write(json.toString());
  175. }
  176.  
  177. // 登录失败时默认返回306状态码
  178. protected void onLoginFail(ServletRequest request, ServletResponse response) throws IOException {
  179. HttpServletResponse httpResponse = (HttpServletResponse) response;
  180. httpResponse.setStatus(HTTP_STATUS_AUTH);
  181. request.setAttribute("msg", "auth check error!");
  182. // 跳转到登录页
  183. redirectToLogin(request, httpResponse);
  184. }
  185.  
  186. @Override
  187. protected void redirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
  188. HttpServletRequest hReq = (HttpServletRequest) request;
  189. String rURL = hReq.getRequestURI();
  190. String errors = StringUtils.isEmpty((String) request.getAttribute("msg")) ? "" : "&msg=" + request.getAttribute("msg");
  191.  
  192. if(request.getAttribute("msg") != null) {
  193. rURL += ((StringUtils.isNotEmpty(hReq.getQueryString())) ?
  194. "&" : "") + "msg=" + request.getAttribute("msg");
  195. }
  196.  
  197. rURL = Base64.encodeBase64URLSafeString(rURL.getBytes()) ;
  198. // 加入登录前地址, 以及错误信息
  199. String loginUrl = getLoginUrl() + "?r=" + rURL + errors;
  200.  
  201. WebUtils.issueRedirect(request, response, loginUrl);
  202. }
  203.  
  204. public boolean include(HttpServletRequest request) {
  205. String u = request.getRequestURI();
  206. for (String e : esc) {
  207. if (u.endsWith(e)) {
  208. return true;
  209. }
  210. }
  211.  
  212. if(StringUtils.isNotBlank(exeludeStr)){
  213. String[] customExcludes = exeludeStr.split(",");
  214. for (String e : customExcludes) {
  215. if (u.endsWith(e)) {
  216. return true;
  217. }
  218. }
  219. }
  220.  
  221. return false;
  222. }
  223.  
  224. @Override
  225. public void afterCompletion(ServletRequest request, ServletResponse response, Exception exception) throws Exception {
  226. super.afterCompletion(request, response, exception);
  227. InvocationInfoProxy.reset();
  228. clearMDC();
  229. }
  230.  
  231. // 设置上下文中的扩展参数,rest传递上下文时生效,Authority header中排除固定key的其它信息都设置到InvocationInfoProxy的parameters
  232. private void initExtendParams(Cookie[] cookies) {
  233. for (Cookie cookie : cookies) {
  234. String cname = cookie.getName();
  235. String cvalue = cookie.getValue();
  236. if(!excludCongtextKeys.contains(cname)){
  237. InvocationInfoProxy.setParameter(cname, cvalue);
  238. }
  239. }
  240. }
  241.  
  242. private void initMDC() {
  243. String userName = "";
  244. Subject subject = SecurityUtils.getSubject();
  245. if (subject != null && subject.getPrincipal() != null) {
  246. userName = (String) SecurityUtils.getSubject().getPrincipal();
  247. }
  248.  
  249. // MDC中记录用户信息
  250. MDC.put(AuthConstants.PARAM_USERNAME, userName);
  251.  
  252. initCustomMDC();
  253. }
  254.  
  255. protected void initCustomMDC() {
  256. MDC.put("InvocationInfoProxy", MapToStringUtil.toEqualString(InvocationInfoProxy.getResources(), ';'));
  257. }
  258.  
  259. protected void afterValidate(HttpServletRequest hReq){
  260. }
  261.  
  262. protected void clearMDC() {
  263. // MDC中记录用户信息
  264. MDC.remove(AuthConstants.PARAM_USERNAME);
  265.  
  266. clearCustomMDC();
  267. }
  268.  
  269. protected void clearCustomMDC() {
  270. MDC.remove("InvocationInfoProxy");
  271. }
  272.  
  273. //初始化 AuthConstants类中定义的常量
  274. {
  275. Field[] fields = AuthConstants.class.getDeclaredFields();
  276. try {
  277. for (Field field : fields) {
  278. field.setAccessible(true);
  279. if (field.getType().toString().endsWith("java.lang.String")
  280. && Modifier.isStatic(field.getModifiers())) {
  281. excludCongtextKeys.add((String) field.get(AuthConstants.class));
  282. }
  283. }
  284. } catch (IllegalAccessException e) {
  285. e.printStackTrace();
  286. }
  287. }
  288. }

  dubbo服务调用时上下文的传递问题

  思路:认证过滤器中 通过MDC将上下文信息写入到InheritableThreadLocal中,写一个dubbo的过滤器。在过滤器中判断,如果是消费一方,则将MDC中的上下文取出来放入dubbo的context变量中;如果是服务方,则从dubbo的context中拿出上下文,解析并放入MDC以及InvocationInfoProxy(下面会提到)类中

  Subject工厂

  1. import org.apache.shiro.subject.Subject;
  2. import org.apache.shiro.subject.SubjectContext;
  3. import org.apache.shiro.web.mgt.DefaultWebSubjectFactory;
  4. public class StatelessDefaultSubjectFactory extends DefaultWebSubjectFactory {
  5. @Override
  6. public Subject createSubject(SubjectContext context) {
  7. //不创建session
  8. context.setSessionCreationEnabled(false);
  9. return super.createSubject(context);
  10. }
  11. }

  同样禁用掉session的创建

  无状态Realm

  1. import com.hjzgg.stateless.auth.session.ShiroSessionManager;
  2. import com.hjzgg.stateless.auth.token.ITokenProcessor;
  3. import com.hjzgg.stateless.auth.token.TokenParameter;
  4. import org.apache.shiro.authc.AuthenticationException;
  5. import org.apache.shiro.authc.AuthenticationInfo;
  6. import org.apache.shiro.authc.AuthenticationToken;
  7. import org.apache.shiro.authc.SimpleAuthenticationInfo;
  8. import org.apache.shiro.authz.AuthorizationInfo;
  9. import org.apache.shiro.authz.SimpleAuthorizationInfo;
  10. import org.apache.shiro.realm.AuthorizingRealm;
  11. import org.apache.shiro.subject.PrincipalCollection;
  12. import org.slf4j.Logger;
  13. import org.slf4j.LoggerFactory;
  14. import org.springframework.beans.factory.annotation.Autowired;
  15.  
  16. import java.util.ArrayList;
  17. import java.util.List;
  18.  
  19. public class StatelessRealm extends AuthorizingRealm {
  20.  
  21. private static final Logger logger = LoggerFactory.getLogger(StatelessRealm.class);
  22.  
  23. @Autowired
  24. private ShiroSessionManager shiroSessionManager;
  25.  
  26. @Override
  27. public boolean supports(AuthenticationToken token) {
  28. // 仅支持StatelessToken类型的Token
  29. return token instanceof StatelessToken;
  30. }
  31.  
  32. @Override
  33. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
  34. SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
  35. List<String> roles = new ArrayList<String>();
  36. info.addRoles(roles);
  37. return info;
  38. }
  39.  
  40. @Override
  41. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken atoken) throws AuthenticationException {
  42. StatelessToken token = (StatelessToken) atoken;
  43. TokenParameter tp = token.getTp();
  44. String userName = (String) token.getPrincipal();
  45. ITokenProcessor tokenProcessor = token.getTokenProcessor();
  46. String tokenStr = tokenProcessor.generateToken(tp);
  47. if (tokenStr == null || !shiroSessionManager.validateOnlineSession(userName, tokenStr)) {
  48. logger.error("User [{}] authenticate fail in System, maybe session timeout!", userName);
  49. throw new AuthenticationException("User " + userName + " authenticate fail in System");
  50. }
  51.  
  52. return new SimpleAuthenticationInfo(userName, tokenStr, getName());
  53. }
  54.  
  55. }

View Code

  这里使用自定义 session manager去校验

  无状态token

  1. import com.hjzgg.stateless.auth.token.ITokenProcessor;
  2. import com.hjzgg.stateless.auth.token.TokenParameter;
  3. import org.apache.shiro.authc.AuthenticationToken;
  4.  
  5. import java.util.Map;
  6.  
  7. public class StatelessToken implements AuthenticationToken {
  8.  
  9. private String userName;
  10. // 预留参数集合,校验更复杂的权限
  11. private Map<String, ?> params;
  12. private String clientDigest;
  13. ITokenProcessor tokenProcessor;
  14. TokenParameter tp;
  15. public StatelessToken(String userName, ITokenProcessor tokenProcessor, TokenParameter tp , Map<String, ?> params, String clientDigest) {
  16. this.userName = userName;
  17. this.params = params;
  18. this.tp = tp;
  19. this.tokenProcessor = tokenProcessor;
  20. this.clientDigest = clientDigest;
  21. }
  22.  
  23. public TokenParameter getTp() {
  24. return tp;
  25. }
  26.  
  27. public void setTp(TokenParameter tp) {
  28. this.tp = tp;
  29. }
  30.  
  31. public String getUserName() {
  32. return userName;
  33. }
  34.  
  35. public void setUserName(String userName) {
  36. this.userName = userName;
  37. }
  38.  
  39. public Map<String, ?> getParams() {
  40. return params;
  41. }
  42.  
  43. public void setParams( Map<String, ?> params) {
  44. this.params = params;
  45. }
  46.  
  47. public String getClientDigest() {
  48. return clientDigest;
  49. }
  50.  
  51. public void setClientDigest(String clientDigest) {
  52. this.clientDigest = clientDigest;
  53. }
  54.  
  55. @Override
  56. public Object getPrincipal() {
  57. return userName;
  58. }
  59.  
  60. @Override
  61. public Object getCredentials() {
  62. return clientDigest;
  63. }
  64.  
  65. public ITokenProcessor getTokenProcessor() {
  66. return tokenProcessor;
  67. }
  68.  
  69. public void setTokenProcessor(ITokenProcessor tokenProcessor) {
  70. this.tokenProcessor = tokenProcessor;
  71. }
  72. }

  token处理器

  1. import com.hjzgg.stateless.auth.session.ShiroSessionManager;
  2. import com.hjzgg.stateless.common.constants.AuthConstants;
  3. import com.hjzgg.stateless.common.esapi.EncryptException;
  4. import com.hjzgg.stateless.common.esapi.IYCPESAPI;
  5. import com.hjzgg.stateless.common.utils.CookieUtil;
  6. import org.apache.commons.codec.binary.Base64;
  7. import org.apache.commons.lang.StringUtils;
  8. import org.slf4j.Logger;
  9. import org.slf4j.LoggerFactory;
  10. import org.springframework.beans.factory.annotation.Autowired;
  11.  
  12. import javax.servlet.http.Cookie;
  13. import java.io.UnsupportedEncodingException;
  14. import java.net.URL;
  15. import java.net.URLEncoder;
  16. import java.util.ArrayList;
  17. import java.util.Iterator;
  18. import java.util.List;
  19. import java.util.Map.Entry;
  20.  
  21. /**
  22. * 默认Token处理器提供将cooke和TokenParameter相互转换,Token生成的能力
  23. * <p>
  24. * 可以注册多个实例
  25. * </p>
  26. *
  27. * @author li
  28. *
  29. */
  30. public class DefaultTokenPorcessor implements ITokenProcessor {
  31. private static Logger log = LoggerFactory.getLogger(DefaultTokenPorcessor.class);
  32. private static int HTTPVERSION = 3;
  33. static {
  34. URL res = DefaultTokenPorcessor.class.getClassLoader().getResource("javax/servlet/annotation/WebServlet.class");
  35. if (res == null) {
  36. HTTPVERSION = 2;
  37. }
  38. }
  39. private String id;
  40. private String domain;
  41. private String path = "/";
  42. private Integer expr;
  43. // 默认迭代次数
  44. private int hashIterations = 2;
  45.  
  46. @Autowired
  47. private ShiroSessionManager shiroSessionManager;
  48.  
  49. @Override
  50. public String getId() {
  51. return id;
  52. }
  53.  
  54. public void setId(String id) {
  55. this.id = id;
  56. }
  57.  
  58. public String getDomain() {
  59. return domain;
  60. }
  61.  
  62. public void setDomain(String domain) {
  63. this.domain = domain;
  64. }
  65.  
  66. public String getPath() {
  67. return path;
  68. }
  69.  
  70. public void setPath(String path) {
  71. this.path = path;
  72. }
  73.  
  74. public Integer getExpr() {
  75. return expr;
  76. }
  77.  
  78. public void setExpr(Integer expr) {
  79. this.expr = expr;
  80. }
  81.  
  82. private List<String> exacts = new ArrayList<String>();
  83.  
  84. public void setExacts(List<String> exacts) {
  85. this.exacts = exacts;
  86. }
  87.  
  88. public int getHashIterations() {
  89. return hashIterations;
  90. }
  91.  
  92. public void setHashIterations(int hashIterations) {
  93. this.hashIterations = hashIterations;
  94. }
  95.  
  96. @Override
  97. public String generateToken(TokenParameter tp) {
  98. try {
  99. String seed = shiroSessionManager.findSeed();
  100. String token = IYCPESAPI.encryptor().hash(
  101. this.id + tp.getUserName() + tp.getLoginTs() + getSummary(tp) + getExpr(),
  102. seed,
  103. getHashIterations());
  104. token = this.id + "," + getExpr() + "," + token;
  105. return Base64.encodeBase64URLSafeString(org.apache.commons.codec.binary.StringUtils.getBytesUtf8(token));
  106. } catch (EncryptException e) {
  107. log.error("TokenParameter is not validate!", e);
  108. throw new IllegalArgumentException("TokenParameter is not validate!");
  109. }
  110. }
  111.  
  112. @Override
  113. public Cookie[] getCookieFromTokenParameter(TokenParameter tp) {
  114. List<Cookie> cookies = new ArrayList<Cookie>();
  115. String tokenStr = generateToken(tp);
  116. Cookie token = new Cookie(AuthConstants.PARAM_TOKEN, tokenStr);
  117. if (HTTPVERSION == 3)
  118. token.setHttpOnly(true);
  119. if (StringUtils.isNotEmpty(domain))
  120. token.setDomain(domain);
  121. token.setPath(path);
  122. cookies.add(token);
  123.  
  124. try {
  125. Cookie userId = new Cookie(AuthConstants.PARAM_USERNAME, URLEncoder.encode(tp.getUserName(), "UTF-8"));
  126. if (StringUtils.isNotEmpty(domain))
  127. userId.setDomain(domain);
  128. userId.setPath(path);
  129. cookies.add(userId);
  130.  
  131. // 登录的时间戳
  132. Cookie logints = new Cookie(AuthConstants.PARAM_LOGINTS, URLEncoder.encode(tp.getLoginTs(), "UTF-8"));
  133. if (StringUtils.isNotEmpty(domain))
  134. logints.setDomain(domain);
  135. logints.setPath(path);
  136. cookies.add(logints);
  137. } catch (UnsupportedEncodingException e) {
  138. log.error("encode error!", e);
  139. }
  140.  
  141. if (!tp.getExt().isEmpty()) {
  142. Iterator<Entry<String, String>> it = tp.getExt().entrySet().iterator();
  143. while (it.hasNext()) {
  144. Entry<String, String> i = it.next();
  145. Cookie ext = new Cookie(i.getKey(), i.getValue());
  146. if (StringUtils.isNotEmpty(domain))
  147. ext.setDomain(domain);
  148. ext.setPath(path);
  149. cookies.add(ext);
  150. }
  151. }
  152.  
  153. shiroSessionManager.registOnlineSession(tp.getUserName(), tokenStr, this);
  154.  
  155. return cookies.toArray(new Cookie[] {});
  156. }
  157.  
  158. @Override
  159. public TokenParameter getTokenParameterFromCookie(Cookie[] cookies) {
  160. TokenParameter tp = new TokenParameter();
  161. String token = CookieUtil.findCookieValue(cookies, AuthConstants.PARAM_TOKEN);
  162. TokenInfo ti = TokenFactory.getTokenInfo(token);
  163. if (ti.getIntegerExpr().intValue() != this.getExpr().intValue()) {
  164. throw new IllegalArgumentException("illegal token!");
  165. }
  166. String userId = CookieUtil.findCookieValue(cookies, AuthConstants.PARAM_USERNAME);
  167. tp.setUserName(userId);
  168. String loginTs = CookieUtil.findCookieValue(cookies, AuthConstants.PARAM_LOGINTS);
  169. tp.setLoginTs(loginTs);
  170.  
  171. if (exacts != null && !exacts.isEmpty()) {
  172. for (int i = 0; i < cookies.length; i++) {
  173. Cookie cookie = cookies[i];
  174. String name = cookie.getName();
  175. if (exacts.contains(name)) {
  176. tp.getExt().put(name,
  177. cookie.getValue() == null ? "" : cookie.getValue());
  178. }
  179. }
  180. }
  181. return tp;
  182. }
  183.  
  184. protected String getSummary(TokenParameter tp) {
  185. if (exacts != null && !exacts.isEmpty()) {
  186. int len = exacts.size();
  187. String[] exa = new String[len];
  188. for (int i = 0; i < len; i++) {
  189. String name = exacts.get(i);
  190. String value = tp.getExt().get(name);
  191. if(value == null) value = "";
  192. exa[i] = value;
  193. }
  194. return StringUtils.join(exa, "#");
  195. }
  196. return "";
  197. }
  198.  
  199. @Override
  200. public Cookie[] getLogoutCookie(String tokenStr, String uid) {
  201. List<Cookie> cookies = new ArrayList<Cookie>();
  202. Cookie token = new Cookie(AuthConstants.PARAM_TOKEN, null);
  203. if (StringUtils.isNotEmpty(domain))
  204. token.setDomain(domain);
  205. token.setPath(path);
  206. cookies.add(token);
  207.  
  208. Cookie userId = new Cookie(AuthConstants.PARAM_USERNAME, null);
  209. if (StringUtils.isNotEmpty(domain))
  210. userId.setDomain(domain);
  211. userId.setPath(path);
  212. cookies.add(userId);
  213.  
  214. // 登录的时间戳
  215. Cookie logints = new Cookie(AuthConstants.PARAM_LOGINTS, null);
  216. if (StringUtils.isNotEmpty(domain))
  217. logints.setDomain(domain);
  218. logints.setPath(path);
  219. cookies.add(logints);
  220. for (String exact : exacts) {
  221. Cookie ext = new Cookie(exact, null);
  222. if (StringUtils.isNotEmpty(domain))
  223. ext.setDomain(domain);
  224. ext.setPath(path);
  225. cookies.add(ext);
  226. }
  227.  
  228. shiroSessionManager.delOnlineSession(uid, tokenStr);
  229.  
  230. return cookies.toArray(new Cookie[] {});
  231. }
  232. }

  将一些必须字段和扩展字段进行通过esapi 的hash算法进行加密,生成token串,最终的token = token处理器标识+过期时间+原token

  shiro配置文件

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:util="http://www.springframework.org/schema/util"
  4. xmlns:aop="http://www.springframework.org/schema/aop"
  5. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  6. xsi:schemaLocation="
  7. http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
  8. http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
  9. http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
  10.  
  11. <bean id="statelessRealm" class="com.hjzgg.stateless.auth.shiro.StatelessRealm">
  12. <property name="cachingEnabled" value="false" />
  13. </bean>
  14.  
  15. <!-- Subject工厂 -->
  16. <bean id="subjectFactory"
  17. class="com.hjzgg.stateless.auth.shiro.StatelessDefaultSubjectFactory" />
  18.  
  19. <bean id="webTokenProcessor" class="com.hjzgg.stateless.auth.token.DefaultTokenPorcessor">
  20. <property name="id" value="web"></property>
  21. <property name="path" value="${context.name}"></property>
  22. <property name="expr" value="${sessionTimeout}"></property>
  23. <property name="exacts">
  24. <list>
  25. <value type="java.lang.String">userType</value>
  26. </list>
  27. </property>
  28. </bean>
  29. <bean id="maTokenProcessor" class="com.hjzgg.stateless.auth.token.DefaultTokenPorcessor">
  30. <property name="id" value="ma"></property>
  31. <property name="path" value="${context.name}"></property>
  32. <property name="expr" value="-1"></property>
  33. <property name="exacts">
  34. <list>
  35. <value type="java.lang.String">userType</value>
  36. </list>
  37. </property>
  38. </bean>
  39.  
  40. <bean id="tokenFactory" class="com.hjzgg.stateless.auth.token.TokenFactory">
  41. <property name="processors">
  42. <list>
  43. <ref bean="webTokenProcessor" />
  44. <ref bean="maTokenProcessor" />
  45. </list>
  46. </property>
  47. </bean>
  48.  
  49. <!-- 会话管理器 -->
  50. <bean id="sessionManager" class="org.apache.shiro.session.mgt.DefaultSessionManager">
  51. <property name="sessionValidationSchedulerEnabled" value="false" />
  52. </bean>
  53.  
  54. <!-- 安全管理器 -->
  55. <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
  56. <property name="realms">
  57. <list>
  58. <ref bean="statelessRealm" />
  59. </list>
  60. </property>
  61. <property name="subjectDAO.sessionStorageEvaluator.sessionStorageEnabled"
  62. value="false" />
  63. <property name="subjectFactory" ref="subjectFactory" />
  64. <property name="sessionManager" ref="sessionManager" />
  65. </bean>
  66.  
  67. <!-- 相当于调用SecurityUtils.setSecurityManager(securityManager) -->
  68. <bean
  69. class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
  70. <property name="staticMethod"
  71. value="org.apache.shiro.SecurityUtils.setSecurityManager" />
  72. <property name="arguments" ref="securityManager" />
  73. </bean>
  74.  
  75. <bean id="statelessAuthcFilter" class="com.hjzgg.stateless.auth.shiro.StatelessAuthcFilter">
  76. <property name="tokenFactory" ref="tokenFactory" />
  77. </bean>
  78.  
  79. <bean id="logout" class="com.hjzgg.stateless.auth.shiro.LogoutFilter"></bean>
  80.  
  81. <!-- Shiro的Web过滤器 -->
  82. <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
  83. <property name="securityManager" ref="securityManager" />
  84. <property name="loginUrl" value="/login" />
  85. <property name="filters">
  86. <util:map>
  87. <entry key="statelessAuthc" value-ref="statelessAuthcFilter" />
  88. </util:map>
  89. </property>
  90. <property name="filterChainDefinitions">
  91. <value>
  92. <!--swagger-->
  93. /webjars/** = anon
  94. /v2/api-docs/** = anon
  95. /swagger-resources/** = anon
  96.  
  97. /login/** = anon
  98. /logout = logout
  99. /static/** = anon
  100. /css/** = anon
  101. /images/** = anon
  102. /trd/** = anon
  103. /js/** = anon
  104. /api/** = anon
  105. /cxf/** = anon
  106. /jaxrs/** = anon
  107. /** = statelessAuthc
  108. </value>
  109. </property>
  110. </bean>
  111. <!-- Shiro生命周期处理器 -->
  112. <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
  113. </beans>

  通过InvocationInfoProxy这个类(基于ThreadLocal的),可以拿到用户相关的参数信息

  1. import com.hjzgg.stateless.common.constants.AuthConstants;
  2.  
  3. import java.util.HashMap;
  4. import java.util.Map;
  5.  
  6. /**
  7. * Created by hujunzheng on 2017/7/18.
  8. */
  9. public class InvocationInfoProxy {
  10. private static final ThreadLocal<Map<String, Object>> resources =
  11. ThreadLocal.withInitial(() -> {
  12. Map<String, Object> initialValue = new HashMap<>();
  13. initialValue.put(AuthConstants.ExtendConstants.PARAM_PARAMETER, new HashMap<String, String>());
  14. return initialValue;
  15. }
  16. );
  17.  
  18. public static String getUserName() {
  19. return (String) resources.get().get(AuthConstants.PARAM_USERNAME);
  20. }
  21.  
  22. public static void setUserName(String userName) {
  23. resources.get().put(AuthConstants.PARAM_USERNAME, userName);
  24. }
  25.  
  26. public static String getLoginTs() {
  27. return (String) resources.get().get(AuthConstants.PARAM_LOGINTS);
  28. }
  29.  
  30. public static void setLoginTs(String loginTs) {
  31. resources.get().put(AuthConstants.PARAM_LOGINTS, loginTs);
  32. }
  33.  
  34. public static String getToken() {
  35. return (String) resources.get().get(AuthConstants.PARAM_TOKEN);
  36. }
  37.  
  38. public static void setToken(String token) {
  39. resources.get().put(AuthConstants.PARAM_TOKEN, token);
  40. }
  41.  
  42. public static void setParameter(String key, String value) {
  43. ((Map<String, String>) resources.get().get(AuthConstants.ExtendConstants.PARAM_PARAMETER)).put(key, value);
  44. }
  45.  
  46. public static String getParameter(String key) {
  47. return ((Map<String, String>) resources.get().get(AuthConstants.ExtendConstants.PARAM_PARAMETER)).get(key);
  48. }
  49.  
  50. public static void reset() {
  51. resources.remove();
  52. }
  53. }

  还有esapi和cache的相关代码到项目里看一下吧

项目地址

  欢迎访问,无状态shiro认证组件

参考拦截

    ESAPI入门使用方法

   Spring MVC 4.2 增加 CORS 支持

  HTTP访问控制(CORS)

  Slf4j MDC 使用和 基于 Logback 的实现分析

无状态shiro认证组件(禁用默认session)的更多相关文章

  1. ASP.NET Core的无状态身份认证框架IdentityServer4

    Identity Server 4是IdentityServer的最新版本,它是流行的OpenID Connect和OAuth Framework for .NET,为ASP.NET Core和.NE ...

  2. shiro jwt 构建无状态分布式鉴权体系

    一:JWT 1.令牌构造 JWT(json web token)是可在网络上传输的用于声明某种主张的令牌(token),以JSON 对象为载体的轻量级开放标准(RFC 7519). 一个JWT令牌的定 ...

  3. Django--用户认证组件auth(登录用-依赖session,其他用)

    一.用户认证组件auth介绍 二.auth_user表添加用户信息 三.auth使用示例 四.auth封装的认证装饰器 一.用户认证组件auth介绍 解决的问题: 之前是把is_login=True放 ...

  4. 37行代码构建无状态组件通信工具-让恼人的Vuex和Redux滚蛋吧!

    状态管理的现状 很多前端开发者认为,Vuex和Redux是用来解决组件间状态通信问题的,所以大部分人仅仅是用于达到状态共享的目的.但是通常Redux是用于解决工程性问题的,用于分离业务与视图,让结构更 ...

  5. 无状态Web应用集成——《跟我学Shiro》

    http://www.tuicool.com/articles/iu2qEf 在一些环境中,可能需要把Web应用做成无状态的,即服务器端无状态,就是说服务器端不会存储像会话这种东西,而是每次请求时带上 ...

  6. 第二十章 无状态Web应用集成——《跟我学Shiro》

    目录贴:跟我学Shiro目录贴 在一些环境中,可能需要把Web应用做成无状态的,即服务器端无状态,就是说服务器端不会存储像会话这种东西,而是每次请求时带上相应的用户名进行登录.如一些REST风格的AP ...

  7. Shiro学习(20)无状态Web应用集成

    在一些环境中,可能需要把Web应用做成无状态的,即服务器端无状态,就是说服务器端不会存储像会话这种东西,而是每次请求时带上相应的用户名进行登录.如一些REST风格的API,如果不使用OAuth2协议, ...

  8. shiro实现无状态的会话,带源码分析

    转载请在页首明显处注明作者与出处 朱小杰      http://www.cnblogs.com/zhuxiaojie/p/7809767.html 一:说明 在网上都找不到相关的信息,还是翻了大半天 ...

  9. 关于Spring Security中无Session和无状态stateless

    Spring Security是J2EE领域使用最广泛的权限框架,支持HTTP BASIC, DIGEST, X509, LDAP, FORM-AUTHENTICATION, OPENID, CAS, ...

随机推荐

  1. mysql 5.7 ~ 新特性

    mysql 5.7特性 简介:mysql 5.7内存和线程性能方面的优化一 细节优化 参数:  1 innodb_buffer_pool    改进 innodb_buffer_pool可以动态扩大, ...

  2. MR数据生成工具指向目录

    mrDataTidy_SaveTwoDays.jar 原始路径 :D:\太原MR数据\一天数据整理 目标路径 : D:\MR现场数据整理\保存两天_整理后数据 例如 当前时间:2017-5-17 10 ...

  3. System.Runtime.InteropServices.COMException (0x800A03EC): 无法访问文件

    使用Microsoft.Office.Interop.Excel 操作 今天在服务器部署,操作程序csv文件转xsl文件的时候,遇到一下问题: System.Runtime.InteropServic ...

  4. 001_ansible通过堡垒机登录

    一. 之前一直通过跳板机登录线上服务器,ssh可以的,如下图所示 vim ~/.ssh/config ssh xx.xx.xx.xx线上服务器是可以的,但是ansible执行显示目标主机不可达,其实a ...

  5. 编译安装lamp环境

    httpd 2.4.9 + mysql-5.5.33 + php-5.4.29编译安装过程: 准备好以下安装包: mysql-5.5.33-linux2.6-x86_64.tar.gz apr-uti ...

  6. apache服务器的常用功能及设置

    安装httpd yum -y install  httpd     服务脚本:/etc/rc.d/init.d/httpd        脚本配置文件:/etc/sysconfig/httpd     ...

  7. 如何优雅打印nginx header和body

    场景 参考https://segmentfault.com/a/1190000000606867可以获取response的报文体,由于业务测试有获取响应头Header或响应体Body的需求,这里是通过 ...

  8. Dubbo入门---搭建一个最简单的Demo框架

    参考文档: https://blog.csdn.net/noaman_wgs/article/details/70214612/

  9. IntelliJ IDEA 下的svn配置及使用的非常详细的图文总结

    首先,使用的时候,自己得先在电脑上安装个小乌龟.也就是svn啦. 第一步安装小乌龟. 如下: 具体安装好像没什么具体要求,一路next,就好. 如上图箭头所示,在安装 TortoiseSVN 的时候, ...

  10. vue scoped 穿透_vue 修改内部组件样式问题

    何为scoped? 在vue文件中的style标签上,有一个特殊的属性:scoped.当一个style标签拥有scoped属性时,它的CSS样式就只能作用于当前的组件,也就是说,该样式只能适用于当前组 ...