无状态shiro认证组件(禁用默认session)
准备内容
简单的shiro无状态认证
无状态认证拦截器
- import com.hjzgg.stateless.shiroSimpleWeb.Constants;
- import com.hjzgg.stateless.shiroSimpleWeb.realm.StatelessToken;
- import org.apache.shiro.web.filter.AccessControlFilter;
- import javax.servlet.ServletRequest;
- import javax.servlet.ServletResponse;
- import javax.servlet.http.HttpServletResponse;
- import java.io.IOException;
- import java.util.HashMap;
- import java.util.Map;
- /**
- * <p>Version: 1.0
- */
- public class StatelessAuthcFilter extends AccessControlFilter {
- @Override
- protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
- return false;
- }
- @Override
- protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
- //1、客户端生成的消息摘要
- String clientDigest = request.getParameter(Constants.PARAM_DIGEST);
- //2、客户端传入的用户身份
- String username = request.getParameter(Constants.PARAM_USERNAME);
- //3、客户端请求的参数列表
- Map<String, String[]> params = new HashMap<String, String[]>(request.getParameterMap());
- params.remove(Constants.PARAM_DIGEST);
- //4、生成无状态Token
- StatelessToken token = new StatelessToken(username, params, clientDigest);
- try {
- //5、委托给Realm进行登录
- getSubject(request, response).login(token);
- } catch (Exception e) {
- e.printStackTrace();
- onLoginFail(response); //6、登录失败
- return false;
- }
- return true;
- }
- //登录失败时默认返回401状态码
- private void onLoginFail(ServletResponse response) throws IOException {
- HttpServletResponse httpResponse = (HttpServletResponse) response;
- httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
- httpResponse.getWriter().write("login error");
- }
- }
Subject工厂
- import org.apache.shiro.subject.Subject;
- import org.apache.shiro.subject.SubjectContext;
- import org.apache.shiro.web.mgt.DefaultWebSubjectFactory;
- /**
- * <p>Version: 1.0
- */
- public class StatelessDefaultSubjectFactory extends DefaultWebSubjectFactory {
- @Override
- public Subject createSubject(SubjectContext context) {
- //不创建session
- context.setSessionCreationEnabled(false);
- return super.createSubject(context);
- }
- }
注意,这里禁用了session
无状态Realm
- import com.hjzgg.stateless.shiroSimpleWeb.codec.HmacSHA256Utils;
- 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;
- /**
- * <p>Version: 1.0
- */
- public class StatelessRealm extends AuthorizingRealm {
- @Override
- public boolean supports(AuthenticationToken token) {
- //仅支持StatelessToken类型的Token
- return token instanceof StatelessToken;
- }
- @Override
- protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
- //根据用户名查找角色,请根据需求实现
- String username = (String) principals.getPrimaryPrincipal();
- SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
- authorizationInfo.addRole("admin");
- return authorizationInfo;
- }
- @Override
- protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
- StatelessToken statelessToken = (StatelessToken) token;
- String username = statelessToken.getUsername();
- String key = getKey(username);//根据用户名获取密钥(和客户端的一样)
- //在服务器端生成客户端参数消息摘要
- String serverDigest = HmacSHA256Utils.digest(key, statelessToken.getParams());
- System.out.println(statelessToken.getClientDigest());
- System.out.println(serverDigest);
- //然后进行客户端消息摘要和服务器端消息摘要的匹配
- return new SimpleAuthenticationInfo(
- username,
- serverDigest,
- getName());
- }
- private String getKey(String username) {//得到密钥,此处硬编码一个
- if("admin".equals(username)) {
- return "dadadswdewq2ewdwqdwadsadasd";
- }
- return null;
- }
- }
无状态Token
- import org.apache.shiro.authc.AuthenticationToken;
- import org.springframework.beans.*;
- import org.springframework.validation.DataBinder;
- import java.util.HashMap;
- import java.util.Map;
- /**
- * <p>Version: 1.0
- */
- public class StatelessToken implements AuthenticationToken {
- private String username;
- private Map<String, ?> params;
- private String clientDigest;
- public StatelessToken(String username, Map<String, ?> params, String clientDigest) {
- this.username = username;
- this.params = params;
- this.clientDigest = clientDigest;
- }
- public String getUsername() {
- return username;
- }
- public void setUsername(String username) {
- this.username = username;
- }
- public Map<String, ?> getParams() {
- return params;
- }
- public void setParams( Map<String, ?> params) {
- this.params = params;
- }
- public String getClientDigest() {
- return clientDigest;
- }
- public void setClientDigest(String clientDigest) {
- this.clientDigest = clientDigest;
- }
- @Override
- public Object getPrincipal() {
- return username;
- }
- @Override
- public Object getCredentials() {
- return clientDigest;
- }
- public static void main(String[] args) {
- }
- public static void test1() {
- StatelessToken token = new StatelessToken(null, null, null);
- BeanWrapperImpl beanWrapper = new BeanWrapperImpl(token);
- beanWrapper.setPropertyValue(new PropertyValue("username", "hjzgg"));
- System.out.println(token.getUsername());
- }
- public static void test2() {
- StatelessToken token = new StatelessToken(null, null, null);
- DataBinder dataBinder = new DataBinder(token);
- Map<String, Object> params = new HashMap<>();
- params.put("username", "hjzgg");
- PropertyValues propertyValues = new MutablePropertyValues(params);
- dataBinder.bind(propertyValues);
- System.out.println(token.getUsername());
- }
- }
shiro配置文件
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:util="http://www.springframework.org/schema/util"
- xmlns:aop="http://www.springframework.org/schema/aop"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="
- http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
- http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
- http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
- <!-- Realm实现 -->
- <bean id="statelessRealm" class="com.hjzgg.stateless.shiroSimpleWeb.realm.StatelessRealm">
- <property name="cachingEnabled" value="false"/>
- </bean>
- <!-- Subject工厂 -->
- <bean id="subjectFactory" class="com.hjzgg.stateless.shiroSimpleWeb.mgt.StatelessDefaultSubjectFactory"/>
- <!-- 会话管理器 -->
- <bean id="sessionManager" class="org.apache.shiro.session.mgt.DefaultSessionManager">
- <property name="sessionValidationSchedulerEnabled" value="false"/>
- </bean>
- <!-- 安全管理器 -->
- <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
- <property name="realm" ref="statelessRealm"/>
- <property name="subjectDAO.sessionStorageEvaluator.sessionStorageEnabled" value="false"/>
- <property name="subjectFactory" ref="subjectFactory"/>
- <property name="sessionManager" ref="sessionManager"/>
- </bean>
- <!-- 相当于调用SecurityUtils.setSecurityManager(securityManager) -->
- <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
- <property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager"/>
- <property name="arguments" ref="securityManager"/>
- </bean>
- <bean id="statelessAuthcFilter" class="com.hjzgg.stateless.shiroSimpleWeb.filter.StatelessAuthcFilter"/>
- <!-- Shiro的Web过滤器 -->
- <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
- <property name="securityManager" ref="securityManager"/>
- <property name="filters">
- <util:map>
- <entry key="statelessAuthc" value-ref="statelessAuthcFilter"/>
- </util:map>
- </property>
- <property name="filterChainDefinitions">
- <value>
- /**=statelessAuthc
- </value>
- </property>
- </bean>
- <!-- Shiro生命周期处理器-->
- <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
- </beans>
这里禁用了回话调度器的session存储
web.xml配置
- <?xml version="1.0" encoding="UTF-8"?>
- <web-app
- xmlns="http://java.sun.com/xml/ns/javaee"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
- version="3.0"
- metadata-complete="false">
- <display-name>shiro-example-chapter20</display-name>
- <!-- Spring配置文件开始 -->
- <context-param>
- <param-name>contextConfigLocation</param-name>
- <param-value>
- classpath:spring-config-shiro.xml
- </param-value>
- </context-param>
- <listener>
- <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
- </listener>
- <!-- Spring配置文件结束 -->
- <!-- shiro 安全过滤器 -->
- <filter>
- <filter-name>shiroFilter</filter-name>
- <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
- <async-supported>true</async-supported>
- <init-param>
- <param-name>targetFilterLifecycle</param-name>
- <param-value>true</param-value>
- </init-param>
- </filter>
- <filter-mapping>
- <filter-name>shiroFilter</filter-name>
- <url-pattern>/*</url-pattern>
- <dispatcher>REQUEST</dispatcher>
- </filter-mapping>
- <servlet>
- <servlet-name>spring</servlet-name>
- <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
- <init-param>
- <param-name>contextConfigLocation</param-name>
- <param-value>classpath:spring-mvc.xml</param-value>
- </init-param>
- <load-on-startup>1</load-on-startup>
- <async-supported>true</async-supported>
- </servlet>
- <servlet-mapping>
- <servlet-name>spring</servlet-name>
- <url-pattern>/</url-pattern>
- </servlet-mapping>
- </web-app>
token生成工具类
- import org.apache.commons.codec.binary.Hex;
- import javax.crypto.Mac;
- import javax.crypto.SecretKey;
- import javax.crypto.spec.SecretKeySpec;
- import java.util.List;
- import java.util.Map;
- /**
- * <p>Version: 1.0
- */
- public class HmacSHA256Utils {
- public static String digest(String key, String content) {
- try {
- Mac mac = Mac.getInstance("HmacSHA256");
- byte[] secretByte = key.getBytes("utf-8");
- byte[] dataBytes = content.getBytes("utf-8");
- SecretKey secret = new SecretKeySpec(secretByte, "HMACSHA256");
- mac.init(secret);
- byte[] doFinal = mac.doFinal(dataBytes);
- byte[] hexB = new Hex().encode(doFinal);
- return new String(hexB, "utf-8");
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
- public static String digest(String key, Map<String, ?> map) {
- StringBuilder s = new StringBuilder();
- for(Object values : map.values()) {
- if(values instanceof String[]) {
- for(String value : (String[])values) {
- s.append(value);
- }
- } else if(values instanceof List) {
- for(String value : (List<String>)values) {
- s.append(value);
- }
- } else {
- s.append(values);
- }
- }
- return digest(key, s.toString());
- }
- }
简单测试一下
- import com.alibaba.fastjson.JSONObject;
- import com.hjzgg.stateless.shiroSimpleWeb.codec.HmacSHA256Utils;
- import com.hjzgg.stateless.shiroSimpleWeb.utils.RestTemplateUtils;
- import org.junit.Test;
- import org.springframework.util.LinkedMultiValueMap;
- import org.springframework.util.MultiValueMap;
- import org.springframework.web.util.UriComponentsBuilder;
- /**
- * <p>Version: 1.0
- */
- public class ClientTest {
- private static final String WEB_URL = "http://localhost:8080/shiro/hello";
- @Test
- public void testServiceHelloSuccess() {
- String username = "admin";
- String param11 = "param11";
- String param12 = "param12";
- String param2 = "param2";
- String key = "dadadswdewq2ewdwqdwadsadasd";
- JSONObject params = new JSONObject();
- params.put(Constants.PARAM_USERNAME, username);
- params.put("param1", param11);
- params.put("param1", param12);
- params.put("param2", param2);
- params.put(Constants.PARAM_DIGEST, HmacSHA256Utils.digest(key, params));
- String result = RestTemplateUtils.get(WEB_URL, params);
- System.out.println(result);
- }
- @Test
- public void testServiceHelloFail() {
- String username = "admin";
- String param11 = "param11";
- String param12 = "param12";
- String param2 = "param2";
- String key = "dadadswdewq2ewdwqdwadsadasd";
- MultiValueMap<String, String> params = new LinkedMultiValueMap<String, String>();
- params.add(Constants.PARAM_USERNAME, username);
- params.add("param1", param11);
- params.add("param1", param12);
- params.add("param2", param2);
- params.add(Constants.PARAM_DIGEST, HmacSHA256Utils.digest(key, params));
- params.set("param2", param2 + "1");
- String url = UriComponentsBuilder
- .fromHttpUrl("http://localhost:8080/hello")
- .queryParams(params).build().toUriString();
- }
- }
补充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逻辑
- import com.hjzgg.stateless.auth.token.ITokenProcessor;
- import com.hjzgg.stateless.auth.token.TokenFactory;
- import com.hjzgg.stateless.auth.token.TokenGenerator;
- import com.hjzgg.stateless.common.cache.RedisCacheTemplate;
- import com.hjzgg.stateless.common.esapi.EncryptException;
- import org.apache.commons.lang3.StringUtils;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.beans.factory.annotation.Value;
- import org.springframework.stereotype.Component;
- @Component
- public class ShiroSessionManager {
- @Autowired
- private RedisCacheTemplate redisCacheTemplate;
- @Value("${sessionMutex}")
- private boolean sessionMutex = false;
- public static final String TOKEN_SEED = "token_seed";
- public static final String DEFAULT_CHARSET = "UTF-8";
- private final Logger logger = LoggerFactory.getLogger(getClass());
- private static String localSeedValue = null;
- /**
- * 获得当前系统的 token seed
- */
- public String findSeed() throws EncryptException {
- if(localSeedValue != null){
- return localSeedValue;
- } else {
- String seed = getSeedValue(TOKEN_SEED);
- if (StringUtils.isBlank(seed)) {
- seed = TokenGenerator.genSeed();
- localSeedValue = seed;
- redisCacheTemplate.put(TOKEN_SEED, seed);
- }
- return seed;
- }
- }
- public String getSeedValue(String key) {
- return (String) redisCacheTemplate.get(key);
- }
- /**
- * 删除session缓存
- *
- * @param sid mock的sessionid
- */
- public void removeSessionCache(String sid) {
- redisCacheTemplate.delete(sid);
- }
- private int getTimeout(String sid){
- return TokenFactory.getTokenInfo(sid).getIntegerExpr();
- }
- private String getCurrentTimeSeconds() {
- return String.valueOf(System.currentTimeMillis()/1000);
- }
- public void registOnlineSession(final String userName, final String token, final ITokenProcessor processor) {
- final String key = userName;
- logger.debug("token processor id is {}, key is {}, sessionMutex is {}!" , processor.getId(), key, sessionMutex);
- // 是否互斥,如果是,则踢掉所有当前用户的session,重新创建,此变量将来从配置文件读取
- if(sessionMutex){
- deleteUserSession(key);
- } else {
- // 清理此用户过期的session,过期的常为异常或者直接关闭浏览器,没有走正常注销的key
- clearOnlineSession(key);
- }
- redisCacheTemplate.hPut(userName, token, getCurrentTimeSeconds());
- int timeout = getTimeout(token);
- if (timeout > 0) {
- redisCacheTemplate.expire(token, timeout);
- }
- }
- private void clearOnlineSession(final String key) {
- redisCacheTemplate.hKeys(key).forEach((obj) -> {
- String hashKey = (String) obj;
- int timeout = getTimeout(hashKey);
- if (timeout > 0) {
- int oldTimeSecondsValue = Integer.valueOf((String) redisCacheTemplate.hGet(key, hashKey));
- int curTimeSecondsValue = (int) (System.currentTimeMillis()/1000);
- //如果 key-hashKey 对应的时间+过期时间 小于 当前时间,则剔除
- if(curTimeSecondsValue - (oldTimeSecondsValue+timeout) > 0) {
- redisCacheTemplate.hDel(key, hashKey);
- }
- }
- });
- }
- public boolean validateOnlineSession(final String key, final String hashKey) {
- int timeout = getTimeout(hashKey);
- if (timeout > 0) {
- String oldTimeSecondsValue = (String) redisCacheTemplate.hGet(key, hashKey);
- if (StringUtils.isEmpty(oldTimeSecondsValue)) {
- return false;
- } else {
- int curTimeSecondsValue = (int) (System.currentTimeMillis()/1000);
- if(Integer.valueOf(oldTimeSecondsValue)+timeout >= curTimeSecondsValue) {
- //刷新 key
- redisCacheTemplate.hPut(key, hashKey, getCurrentTimeSeconds());
- redisCacheTemplate.expire(key, timeout);
- return true;
- } else {
- redisCacheTemplate.hDel(key, hashKey);
- return false;
- }
- }
- } else {
- return redisCacheTemplate.hGet(key, hashKey) != null;
- }
- }
- // 注销用户时候需要调用
- public void delOnlineSession(final String key, final String hashKey){
- redisCacheTemplate.hDel(key, hashKey);
- }
- // 禁用或者删除用户时候调用
- public void deleteUserSession(final String key){
- redisCacheTemplate.delete(key);
- }
- }
无状态认证过滤器
- package com.hjzgg.stateless.auth.shiro;
- import com.alibaba.fastjson.JSONObject;
- import com.hjzgg.stateless.auth.token.ITokenProcessor;
- import com.hjzgg.stateless.auth.token.TokenFactory;
- import com.hjzgg.stateless.auth.token.TokenParameter;
- import com.hjzgg.stateless.common.constants.AuthConstants;
- import com.hjzgg.stateless.common.utils.CookieUtil;
- import com.hjzgg.stateless.common.utils.InvocationInfoProxy;
- import com.hjzgg.stateless.common.utils.MapToStringUtil;
- import org.apache.commons.codec.binary.Base64;
- import org.apache.commons.lang3.StringUtils;
- import org.apache.shiro.SecurityUtils;
- import org.apache.shiro.authc.AuthenticationException;
- import org.apache.shiro.subject.Subject;
- import org.apache.shiro.web.filter.AccessControlFilter;
- import org.apache.shiro.web.util.WebUtils;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import org.slf4j.MDC;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.beans.factory.annotation.Value;
- import javax.servlet.ServletRequest;
- import javax.servlet.ServletResponse;
- import javax.servlet.http.Cookie;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import java.io.IOException;
- import java.lang.reflect.Field;
- import java.lang.reflect.Modifier;
- import java.net.URL;
- import java.util.*;
- public class StatelessAuthcFilter extends AccessControlFilter {
- private static final Logger log = LoggerFactory.getLogger(StatelessAuthcFilter.class);
- public static final int HTTP_STATUS_AUTH = 306;
- @Value("${filterExclude}")
- private String exeludeStr;
- @Autowired
- private TokenFactory tokenFactory;
- private String[] esc = new String[] {
- "/logout","/login","/formLogin",".jpg",".png",".gif",".css",".js",".jpeg"
- };
- private List<String> excludCongtextKeys = new ArrayList<>();
- public void setTokenFactory(TokenFactory tokenFactory) {
- this.tokenFactory = tokenFactory;
- }
- public void setEsc(String[] esc) {
- this.esc = esc;
- }
- public void setExcludCongtextKeys(List<String> excludCongtextKeys) {
- this.excludCongtextKeys = excludCongtextKeys;
- }
- @Override
- protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
- return false;
- }
- @Override
- protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
- boolean isAjax = isAjax(request);
- // 1、客户端发送来的摘要
- HttpServletRequest hReq = (HttpServletRequest) request;
- HttpServletRequest httpRequest = hReq;
- Cookie[] cookies = httpRequest.getCookies();
- String authority = httpRequest.getHeader("Authority");
- //如果header中包含,则以header为主,否则,以cookie为主
- if(StringUtils.isNotBlank(authority)){
- Set<Cookie> cookieSet = new HashSet<Cookie>();
- String[] ac = authority.split(";");
- for(String s : ac){
- String[] cookieArr = s.split("=");
- String key = StringUtils.trim(cookieArr[0]);
- String value = StringUtils.trim(cookieArr[1]);
- Cookie cookie = new Cookie(key, value);
- cookieSet.add(cookie);
- }
- cookies = cookieSet.toArray(new Cookie[]{});
- }
- String tokenStr = CookieUtil.findCookieValue(cookies, AuthConstants.PARAM_TOKEN);
- String cookieUserName = CookieUtil.findCookieValue(cookies, AuthConstants.PARAM_USERNAME);
- String loginTs = CookieUtil.findCookieValue(cookies, AuthConstants.PARAM_LOGINTS);
- // 2、客户端传入的用户身份
- String userName = request.getParameter(AuthConstants.PARAM_USERNAME);
- if (userName == null && StringUtils.isNotBlank(cookieUserName)) {
- userName = cookieUserName;
- }
- boolean needCheck = !include(hReq);
- if (needCheck) {
- if (StringUtils.isEmpty(tokenStr) || StringUtils.isEmpty(userName)) {
- if (isAjax) {
- onAjaxAuthFail(request, response);
- } else {
- onLoginFail(request, response);
- }
- return false;
- }
- // 3、客户端请求的参数列表
- Map<String, String[]> params = new HashMap<String, String[]>(request.getParameterMap());
- ITokenProcessor tokenProcessor = tokenFactory.getTokenProcessor(tokenStr);
- TokenParameter tp = tokenProcessor.getTokenParameterFromCookie(cookies);
- // 4、生成无状态Token
- StatelessToken token = new StatelessToken(userName, tokenProcessor, tp, params, new String(tokenStr));
- try {
- // 5、委托给Realm进行登录
- getSubject(request, response).login(token); // 这个地方应该验证上下文信息中的正确性
- // 设置上下文变量
- InvocationInfoProxy.setUserName(userName);
- InvocationInfoProxy.setLoginTs(loginTs);
- InvocationInfoProxy.setToken(tokenStr);
- //设置上下文携带的额外属性
- initExtendParams(cookies);
- initMDC();
- afterValidate(hReq);
- } catch (Exception e) {
- log.error(e.getMessage(), e);
- if (isAjax && e instanceof AuthenticationException) {
- onAjaxAuthFail(request, response); // 6、验证失败,返回ajax调用方信息
- return false;
- } else {
- onLoginFail(request, response); // 6、登录失败,跳转到登录页
- return false;
- }
- }
- return true;
- } else {
- return true;
- }
- }
- private boolean isAjax(ServletRequest request) {
- boolean isAjax = false;
- if (request instanceof HttpServletRequest) {
- HttpServletRequest rq = (HttpServletRequest) request;
- String requestType = rq.getHeader("X-Requested-With");
- if (requestType != null && "XMLHttpRequest".equals(requestType)) {
- isAjax = true;
- }
- }
- return isAjax;
- }
- protected void onAjaxAuthFail(ServletRequest request, ServletResponse resp) throws IOException {
- HttpServletResponse response = (HttpServletResponse) resp;
- JSONObject json = new JSONObject();
- json.put("msg", "auth check error!");
- response.setStatus(HTTP_STATUS_AUTH);
- response.getWriter().write(json.toString());
- }
- // 登录失败时默认返回306状态码
- protected void onLoginFail(ServletRequest request, ServletResponse response) throws IOException {
- HttpServletResponse httpResponse = (HttpServletResponse) response;
- httpResponse.setStatus(HTTP_STATUS_AUTH);
- request.setAttribute("msg", "auth check error!");
- // 跳转到登录页
- redirectToLogin(request, httpResponse);
- }
- @Override
- protected void redirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
- HttpServletRequest hReq = (HttpServletRequest) request;
- String rURL = hReq.getRequestURI();
- String errors = StringUtils.isEmpty((String) request.getAttribute("msg")) ? "" : "&msg=" + request.getAttribute("msg");
- if(request.getAttribute("msg") != null) {
- rURL += ((StringUtils.isNotEmpty(hReq.getQueryString())) ?
- "&" : "") + "msg=" + request.getAttribute("msg");
- }
- rURL = Base64.encodeBase64URLSafeString(rURL.getBytes()) ;
- // 加入登录前地址, 以及错误信息
- String loginUrl = getLoginUrl() + "?r=" + rURL + errors;
- WebUtils.issueRedirect(request, response, loginUrl);
- }
- public boolean include(HttpServletRequest request) {
- String u = request.getRequestURI();
- for (String e : esc) {
- if (u.endsWith(e)) {
- return true;
- }
- }
- if(StringUtils.isNotBlank(exeludeStr)){
- String[] customExcludes = exeludeStr.split(",");
- for (String e : customExcludes) {
- if (u.endsWith(e)) {
- return true;
- }
- }
- }
- return false;
- }
- @Override
- public void afterCompletion(ServletRequest request, ServletResponse response, Exception exception) throws Exception {
- super.afterCompletion(request, response, exception);
- InvocationInfoProxy.reset();
- clearMDC();
- }
- // 设置上下文中的扩展参数,rest传递上下文时生效,Authority header中排除固定key的其它信息都设置到InvocationInfoProxy的parameters
- private void initExtendParams(Cookie[] cookies) {
- for (Cookie cookie : cookies) {
- String cname = cookie.getName();
- String cvalue = cookie.getValue();
- if(!excludCongtextKeys.contains(cname)){
- InvocationInfoProxy.setParameter(cname, cvalue);
- }
- }
- }
- private void initMDC() {
- String userName = "";
- Subject subject = SecurityUtils.getSubject();
- if (subject != null && subject.getPrincipal() != null) {
- userName = (String) SecurityUtils.getSubject().getPrincipal();
- }
- // MDC中记录用户信息
- MDC.put(AuthConstants.PARAM_USERNAME, userName);
- initCustomMDC();
- }
- protected void initCustomMDC() {
- MDC.put("InvocationInfoProxy", MapToStringUtil.toEqualString(InvocationInfoProxy.getResources(), ';'));
- }
- protected void afterValidate(HttpServletRequest hReq){
- }
- protected void clearMDC() {
- // MDC中记录用户信息
- MDC.remove(AuthConstants.PARAM_USERNAME);
- clearCustomMDC();
- }
- protected void clearCustomMDC() {
- MDC.remove("InvocationInfoProxy");
- }
- //初始化 AuthConstants类中定义的常量
- {
- Field[] fields = AuthConstants.class.getDeclaredFields();
- try {
- for (Field field : fields) {
- field.setAccessible(true);
- if (field.getType().toString().endsWith("java.lang.String")
- && Modifier.isStatic(field.getModifiers())) {
- excludCongtextKeys.add((String) field.get(AuthConstants.class));
- }
- }
- } catch (IllegalAccessException e) {
- e.printStackTrace();
- }
- }
- }
dubbo服务调用时上下文的传递问题
思路:认证过滤器中 通过MDC将上下文信息写入到InheritableThreadLocal中,写一个dubbo的过滤器。在过滤器中判断,如果是消费一方,则将MDC中的上下文取出来放入dubbo的context变量中;如果是服务方,则从dubbo的context中拿出上下文,解析并放入MDC以及InvocationInfoProxy(下面会提到)类中
Subject工厂
- import org.apache.shiro.subject.Subject;
- import org.apache.shiro.subject.SubjectContext;
- import org.apache.shiro.web.mgt.DefaultWebSubjectFactory;
- public class StatelessDefaultSubjectFactory extends DefaultWebSubjectFactory {
- @Override
- public Subject createSubject(SubjectContext context) {
- //不创建session
- context.setSessionCreationEnabled(false);
- return super.createSubject(context);
- }
- }
同样禁用掉session的创建
无状态Realm
- import com.hjzgg.stateless.auth.session.ShiroSessionManager;
- import com.hjzgg.stateless.auth.token.ITokenProcessor;
- import com.hjzgg.stateless.auth.token.TokenParameter;
- 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;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import org.springframework.beans.factory.annotation.Autowired;
- import java.util.ArrayList;
- import java.util.List;
- public class StatelessRealm extends AuthorizingRealm {
- private static final Logger logger = LoggerFactory.getLogger(StatelessRealm.class);
- @Autowired
- private ShiroSessionManager shiroSessionManager;
- @Override
- public boolean supports(AuthenticationToken token) {
- // 仅支持StatelessToken类型的Token
- return token instanceof StatelessToken;
- }
- @Override
- protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
- SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
- List<String> roles = new ArrayList<String>();
- info.addRoles(roles);
- return info;
- }
- @Override
- protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken atoken) throws AuthenticationException {
- StatelessToken token = (StatelessToken) atoken;
- TokenParameter tp = token.getTp();
- String userName = (String) token.getPrincipal();
- ITokenProcessor tokenProcessor = token.getTokenProcessor();
- String tokenStr = tokenProcessor.generateToken(tp);
- if (tokenStr == null || !shiroSessionManager.validateOnlineSession(userName, tokenStr)) {
- logger.error("User [{}] authenticate fail in System, maybe session timeout!", userName);
- throw new AuthenticationException("User " + userName + " authenticate fail in System");
- }
- return new SimpleAuthenticationInfo(userName, tokenStr, getName());
- }
- }
View Code
这里使用自定义 session manager去校验
无状态token
- import com.hjzgg.stateless.auth.token.ITokenProcessor;
- import com.hjzgg.stateless.auth.token.TokenParameter;
- import org.apache.shiro.authc.AuthenticationToken;
- import java.util.Map;
- public class StatelessToken implements AuthenticationToken {
- private String userName;
- // 预留参数集合,校验更复杂的权限
- private Map<String, ?> params;
- private String clientDigest;
- ITokenProcessor tokenProcessor;
- TokenParameter tp;
- public StatelessToken(String userName, ITokenProcessor tokenProcessor, TokenParameter tp , Map<String, ?> params, String clientDigest) {
- this.userName = userName;
- this.params = params;
- this.tp = tp;
- this.tokenProcessor = tokenProcessor;
- this.clientDigest = clientDigest;
- }
- public TokenParameter getTp() {
- return tp;
- }
- public void setTp(TokenParameter tp) {
- this.tp = tp;
- }
- public String getUserName() {
- return userName;
- }
- public void setUserName(String userName) {
- this.userName = userName;
- }
- public Map<String, ?> getParams() {
- return params;
- }
- public void setParams( Map<String, ?> params) {
- this.params = params;
- }
- public String getClientDigest() {
- return clientDigest;
- }
- public void setClientDigest(String clientDigest) {
- this.clientDigest = clientDigest;
- }
- @Override
- public Object getPrincipal() {
- return userName;
- }
- @Override
- public Object getCredentials() {
- return clientDigest;
- }
- public ITokenProcessor getTokenProcessor() {
- return tokenProcessor;
- }
- public void setTokenProcessor(ITokenProcessor tokenProcessor) {
- this.tokenProcessor = tokenProcessor;
- }
- }
token处理器
- import com.hjzgg.stateless.auth.session.ShiroSessionManager;
- import com.hjzgg.stateless.common.constants.AuthConstants;
- import com.hjzgg.stateless.common.esapi.EncryptException;
- import com.hjzgg.stateless.common.esapi.IYCPESAPI;
- import com.hjzgg.stateless.common.utils.CookieUtil;
- import org.apache.commons.codec.binary.Base64;
- import org.apache.commons.lang.StringUtils;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import org.springframework.beans.factory.annotation.Autowired;
- import javax.servlet.http.Cookie;
- import java.io.UnsupportedEncodingException;
- import java.net.URL;
- import java.net.URLEncoder;
- import java.util.ArrayList;
- import java.util.Iterator;
- import java.util.List;
- import java.util.Map.Entry;
- /**
- * 默认Token处理器提供将cooke和TokenParameter相互转换,Token生成的能力
- * <p>
- * 可以注册多个实例
- * </p>
- *
- * @author li
- *
- */
- public class DefaultTokenPorcessor implements ITokenProcessor {
- private static Logger log = LoggerFactory.getLogger(DefaultTokenPorcessor.class);
- private static int HTTPVERSION = 3;
- static {
- URL res = DefaultTokenPorcessor.class.getClassLoader().getResource("javax/servlet/annotation/WebServlet.class");
- if (res == null) {
- HTTPVERSION = 2;
- }
- }
- private String id;
- private String domain;
- private String path = "/";
- private Integer expr;
- // 默认迭代次数
- private int hashIterations = 2;
- @Autowired
- private ShiroSessionManager shiroSessionManager;
- @Override
- public String getId() {
- return id;
- }
- public void setId(String id) {
- this.id = id;
- }
- public String getDomain() {
- return domain;
- }
- public void setDomain(String domain) {
- this.domain = domain;
- }
- public String getPath() {
- return path;
- }
- public void setPath(String path) {
- this.path = path;
- }
- public Integer getExpr() {
- return expr;
- }
- public void setExpr(Integer expr) {
- this.expr = expr;
- }
- private List<String> exacts = new ArrayList<String>();
- public void setExacts(List<String> exacts) {
- this.exacts = exacts;
- }
- public int getHashIterations() {
- return hashIterations;
- }
- public void setHashIterations(int hashIterations) {
- this.hashIterations = hashIterations;
- }
- @Override
- public String generateToken(TokenParameter tp) {
- try {
- String seed = shiroSessionManager.findSeed();
- String token = IYCPESAPI.encryptor().hash(
- this.id + tp.getUserName() + tp.getLoginTs() + getSummary(tp) + getExpr(),
- seed,
- getHashIterations());
- token = this.id + "," + getExpr() + "," + token;
- return Base64.encodeBase64URLSafeString(org.apache.commons.codec.binary.StringUtils.getBytesUtf8(token));
- } catch (EncryptException e) {
- log.error("TokenParameter is not validate!", e);
- throw new IllegalArgumentException("TokenParameter is not validate!");
- }
- }
- @Override
- public Cookie[] getCookieFromTokenParameter(TokenParameter tp) {
- List<Cookie> cookies = new ArrayList<Cookie>();
- String tokenStr = generateToken(tp);
- Cookie token = new Cookie(AuthConstants.PARAM_TOKEN, tokenStr);
- if (HTTPVERSION == 3)
- token.setHttpOnly(true);
- if (StringUtils.isNotEmpty(domain))
- token.setDomain(domain);
- token.setPath(path);
- cookies.add(token);
- try {
- Cookie userId = new Cookie(AuthConstants.PARAM_USERNAME, URLEncoder.encode(tp.getUserName(), "UTF-8"));
- if (StringUtils.isNotEmpty(domain))
- userId.setDomain(domain);
- userId.setPath(path);
- cookies.add(userId);
- // 登录的时间戳
- Cookie logints = new Cookie(AuthConstants.PARAM_LOGINTS, URLEncoder.encode(tp.getLoginTs(), "UTF-8"));
- if (StringUtils.isNotEmpty(domain))
- logints.setDomain(domain);
- logints.setPath(path);
- cookies.add(logints);
- } catch (UnsupportedEncodingException e) {
- log.error("encode error!", e);
- }
- if (!tp.getExt().isEmpty()) {
- Iterator<Entry<String, String>> it = tp.getExt().entrySet().iterator();
- while (it.hasNext()) {
- Entry<String, String> i = it.next();
- Cookie ext = new Cookie(i.getKey(), i.getValue());
- if (StringUtils.isNotEmpty(domain))
- ext.setDomain(domain);
- ext.setPath(path);
- cookies.add(ext);
- }
- }
- shiroSessionManager.registOnlineSession(tp.getUserName(), tokenStr, this);
- return cookies.toArray(new Cookie[] {});
- }
- @Override
- public TokenParameter getTokenParameterFromCookie(Cookie[] cookies) {
- TokenParameter tp = new TokenParameter();
- String token = CookieUtil.findCookieValue(cookies, AuthConstants.PARAM_TOKEN);
- TokenInfo ti = TokenFactory.getTokenInfo(token);
- if (ti.getIntegerExpr().intValue() != this.getExpr().intValue()) {
- throw new IllegalArgumentException("illegal token!");
- }
- String userId = CookieUtil.findCookieValue(cookies, AuthConstants.PARAM_USERNAME);
- tp.setUserName(userId);
- String loginTs = CookieUtil.findCookieValue(cookies, AuthConstants.PARAM_LOGINTS);
- tp.setLoginTs(loginTs);
- if (exacts != null && !exacts.isEmpty()) {
- for (int i = 0; i < cookies.length; i++) {
- Cookie cookie = cookies[i];
- String name = cookie.getName();
- if (exacts.contains(name)) {
- tp.getExt().put(name,
- cookie.getValue() == null ? "" : cookie.getValue());
- }
- }
- }
- return tp;
- }
- protected String getSummary(TokenParameter tp) {
- if (exacts != null && !exacts.isEmpty()) {
- int len = exacts.size();
- String[] exa = new String[len];
- for (int i = 0; i < len; i++) {
- String name = exacts.get(i);
- String value = tp.getExt().get(name);
- if(value == null) value = "";
- exa[i] = value;
- }
- return StringUtils.join(exa, "#");
- }
- return "";
- }
- @Override
- public Cookie[] getLogoutCookie(String tokenStr, String uid) {
- List<Cookie> cookies = new ArrayList<Cookie>();
- Cookie token = new Cookie(AuthConstants.PARAM_TOKEN, null);
- if (StringUtils.isNotEmpty(domain))
- token.setDomain(domain);
- token.setPath(path);
- cookies.add(token);
- Cookie userId = new Cookie(AuthConstants.PARAM_USERNAME, null);
- if (StringUtils.isNotEmpty(domain))
- userId.setDomain(domain);
- userId.setPath(path);
- cookies.add(userId);
- // 登录的时间戳
- Cookie logints = new Cookie(AuthConstants.PARAM_LOGINTS, null);
- if (StringUtils.isNotEmpty(domain))
- logints.setDomain(domain);
- logints.setPath(path);
- cookies.add(logints);
- for (String exact : exacts) {
- Cookie ext = new Cookie(exact, null);
- if (StringUtils.isNotEmpty(domain))
- ext.setDomain(domain);
- ext.setPath(path);
- cookies.add(ext);
- }
- shiroSessionManager.delOnlineSession(uid, tokenStr);
- return cookies.toArray(new Cookie[] {});
- }
- }
将一些必须字段和扩展字段进行通过esapi 的hash算法进行加密,生成token串,最终的token = token处理器标识+过期时间+原token
shiro配置文件
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:util="http://www.springframework.org/schema/util"
- xmlns:aop="http://www.springframework.org/schema/aop"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="
- http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
- http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
- http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
- <bean id="statelessRealm" class="com.hjzgg.stateless.auth.shiro.StatelessRealm">
- <property name="cachingEnabled" value="false" />
- </bean>
- <!-- Subject工厂 -->
- <bean id="subjectFactory"
- class="com.hjzgg.stateless.auth.shiro.StatelessDefaultSubjectFactory" />
- <bean id="webTokenProcessor" class="com.hjzgg.stateless.auth.token.DefaultTokenPorcessor">
- <property name="id" value="web"></property>
- <property name="path" value="${context.name}"></property>
- <property name="expr" value="${sessionTimeout}"></property>
- <property name="exacts">
- <list>
- <value type="java.lang.String">userType</value>
- </list>
- </property>
- </bean>
- <bean id="maTokenProcessor" class="com.hjzgg.stateless.auth.token.DefaultTokenPorcessor">
- <property name="id" value="ma"></property>
- <property name="path" value="${context.name}"></property>
- <property name="expr" value="-1"></property>
- <property name="exacts">
- <list>
- <value type="java.lang.String">userType</value>
- </list>
- </property>
- </bean>
- <bean id="tokenFactory" class="com.hjzgg.stateless.auth.token.TokenFactory">
- <property name="processors">
- <list>
- <ref bean="webTokenProcessor" />
- <ref bean="maTokenProcessor" />
- </list>
- </property>
- </bean>
- <!-- 会话管理器 -->
- <bean id="sessionManager" class="org.apache.shiro.session.mgt.DefaultSessionManager">
- <property name="sessionValidationSchedulerEnabled" value="false" />
- </bean>
- <!-- 安全管理器 -->
- <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
- <property name="realms">
- <list>
- <ref bean="statelessRealm" />
- </list>
- </property>
- <property name="subjectDAO.sessionStorageEvaluator.sessionStorageEnabled"
- value="false" />
- <property name="subjectFactory" ref="subjectFactory" />
- <property name="sessionManager" ref="sessionManager" />
- </bean>
- <!-- 相当于调用SecurityUtils.setSecurityManager(securityManager) -->
- <bean
- class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
- <property name="staticMethod"
- value="org.apache.shiro.SecurityUtils.setSecurityManager" />
- <property name="arguments" ref="securityManager" />
- </bean>
- <bean id="statelessAuthcFilter" class="com.hjzgg.stateless.auth.shiro.StatelessAuthcFilter">
- <property name="tokenFactory" ref="tokenFactory" />
- </bean>
- <bean id="logout" class="com.hjzgg.stateless.auth.shiro.LogoutFilter"></bean>
- <!-- Shiro的Web过滤器 -->
- <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
- <property name="securityManager" ref="securityManager" />
- <property name="loginUrl" value="/login" />
- <property name="filters">
- <util:map>
- <entry key="statelessAuthc" value-ref="statelessAuthcFilter" />
- </util:map>
- </property>
- <property name="filterChainDefinitions">
- <value>
- <!--swagger-->
- /webjars/** = anon
- /v2/api-docs/** = anon
- /swagger-resources/** = anon
- /login/** = anon
- /logout = logout
- /static/** = anon
- /css/** = anon
- /images/** = anon
- /trd/** = anon
- /js/** = anon
- /api/** = anon
- /cxf/** = anon
- /jaxrs/** = anon
- /** = statelessAuthc
- </value>
- </property>
- </bean>
- <!-- Shiro生命周期处理器 -->
- <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
- </beans>
通过InvocationInfoProxy这个类(基于ThreadLocal的),可以拿到用户相关的参数信息
- import com.hjzgg.stateless.common.constants.AuthConstants;
- import java.util.HashMap;
- import java.util.Map;
- /**
- * Created by hujunzheng on 2017/7/18.
- */
- public class InvocationInfoProxy {
- private static final ThreadLocal<Map<String, Object>> resources =
- ThreadLocal.withInitial(() -> {
- Map<String, Object> initialValue = new HashMap<>();
- initialValue.put(AuthConstants.ExtendConstants.PARAM_PARAMETER, new HashMap<String, String>());
- return initialValue;
- }
- );
- public static String getUserName() {
- return (String) resources.get().get(AuthConstants.PARAM_USERNAME);
- }
- public static void setUserName(String userName) {
- resources.get().put(AuthConstants.PARAM_USERNAME, userName);
- }
- public static String getLoginTs() {
- return (String) resources.get().get(AuthConstants.PARAM_LOGINTS);
- }
- public static void setLoginTs(String loginTs) {
- resources.get().put(AuthConstants.PARAM_LOGINTS, loginTs);
- }
- public static String getToken() {
- return (String) resources.get().get(AuthConstants.PARAM_TOKEN);
- }
- public static void setToken(String token) {
- resources.get().put(AuthConstants.PARAM_TOKEN, token);
- }
- public static void setParameter(String key, String value) {
- ((Map<String, String>) resources.get().get(AuthConstants.ExtendConstants.PARAM_PARAMETER)).put(key, value);
- }
- public static String getParameter(String key) {
- return ((Map<String, String>) resources.get().get(AuthConstants.ExtendConstants.PARAM_PARAMETER)).get(key);
- }
- public static void reset() {
- resources.remove();
- }
- }
还有esapi和cache的相关代码到项目里看一下吧
项目地址
欢迎访问,无状态shiro认证组件!
参考拦截
Slf4j MDC 使用和 基于 Logback 的实现分析
无状态shiro认证组件(禁用默认session)的更多相关文章
- ASP.NET Core的无状态身份认证框架IdentityServer4
Identity Server 4是IdentityServer的最新版本,它是流行的OpenID Connect和OAuth Framework for .NET,为ASP.NET Core和.NE ...
- shiro jwt 构建无状态分布式鉴权体系
一:JWT 1.令牌构造 JWT(json web token)是可在网络上传输的用于声明某种主张的令牌(token),以JSON 对象为载体的轻量级开放标准(RFC 7519). 一个JWT令牌的定 ...
- Django--用户认证组件auth(登录用-依赖session,其他用)
一.用户认证组件auth介绍 二.auth_user表添加用户信息 三.auth使用示例 四.auth封装的认证装饰器 一.用户认证组件auth介绍 解决的问题: 之前是把is_login=True放 ...
- 37行代码构建无状态组件通信工具-让恼人的Vuex和Redux滚蛋吧!
状态管理的现状 很多前端开发者认为,Vuex和Redux是用来解决组件间状态通信问题的,所以大部分人仅仅是用于达到状态共享的目的.但是通常Redux是用于解决工程性问题的,用于分离业务与视图,让结构更 ...
- 无状态Web应用集成——《跟我学Shiro》
http://www.tuicool.com/articles/iu2qEf 在一些环境中,可能需要把Web应用做成无状态的,即服务器端无状态,就是说服务器端不会存储像会话这种东西,而是每次请求时带上 ...
- 第二十章 无状态Web应用集成——《跟我学Shiro》
目录贴:跟我学Shiro目录贴 在一些环境中,可能需要把Web应用做成无状态的,即服务器端无状态,就是说服务器端不会存储像会话这种东西,而是每次请求时带上相应的用户名进行登录.如一些REST风格的AP ...
- Shiro学习(20)无状态Web应用集成
在一些环境中,可能需要把Web应用做成无状态的,即服务器端无状态,就是说服务器端不会存储像会话这种东西,而是每次请求时带上相应的用户名进行登录.如一些REST风格的API,如果不使用OAuth2协议, ...
- shiro实现无状态的会话,带源码分析
转载请在页首明显处注明作者与出处 朱小杰 http://www.cnblogs.com/zhuxiaojie/p/7809767.html 一:说明 在网上都找不到相关的信息,还是翻了大半天 ...
- 关于Spring Security中无Session和无状态stateless
Spring Security是J2EE领域使用最广泛的权限框架,支持HTTP BASIC, DIGEST, X509, LDAP, FORM-AUTHENTICATION, OPENID, CAS, ...
随机推荐
- mysql 5.7 ~ 新特性
mysql 5.7特性 简介:mysql 5.7内存和线程性能方面的优化一 细节优化 参数: 1 innodb_buffer_pool 改进 innodb_buffer_pool可以动态扩大, ...
- MR数据生成工具指向目录
mrDataTidy_SaveTwoDays.jar 原始路径 :D:\太原MR数据\一天数据整理 目标路径 : D:\MR现场数据整理\保存两天_整理后数据 例如 当前时间:2017-5-17 10 ...
- System.Runtime.InteropServices.COMException (0x800A03EC): 无法访问文件
使用Microsoft.Office.Interop.Excel 操作 今天在服务器部署,操作程序csv文件转xsl文件的时候,遇到一下问题: System.Runtime.InteropServic ...
- 001_ansible通过堡垒机登录
一. 之前一直通过跳板机登录线上服务器,ssh可以的,如下图所示 vim ~/.ssh/config ssh xx.xx.xx.xx线上服务器是可以的,但是ansible执行显示目标主机不可达,其实a ...
- 编译安装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 ...
- apache服务器的常用功能及设置
安装httpd yum -y install httpd 服务脚本:/etc/rc.d/init.d/httpd 脚本配置文件:/etc/sysconfig/httpd ...
- 如何优雅打印nginx header和body
场景 参考https://segmentfault.com/a/1190000000606867可以获取response的报文体,由于业务测试有获取响应头Header或响应体Body的需求,这里是通过 ...
- Dubbo入门---搭建一个最简单的Demo框架
参考文档: https://blog.csdn.net/noaman_wgs/article/details/70214612/
- IntelliJ IDEA 下的svn配置及使用的非常详细的图文总结
首先,使用的时候,自己得先在电脑上安装个小乌龟.也就是svn啦. 第一步安装小乌龟. 如下: 具体安装好像没什么具体要求,一路next,就好. 如上图箭头所示,在安装 TortoiseSVN 的时候, ...
- vue scoped 穿透_vue 修改内部组件样式问题
何为scoped? 在vue文件中的style标签上,有一个特殊的属性:scoped.当一个style标签拥有scoped属性时,它的CSS样式就只能作用于当前的组件,也就是说,该样式只能适用于当前组 ...