Springboot + Spring Security + jwt-token实现权限认证

Springboot + Spring Security + jwt-token实现权限认证

 欢迎大家去我的个人网站踩踩 点这里哦

一、前言

本项目默认是用session认证用户的,但是源于要开放某些接口给其他系统调用,故想在保留原先session认证的基础上,对部分接口使用jwt-token认证。参考了网上的一些资料,针对自己项目实际情况实现如下。

二、解决思路

其实网上很多不是Spring Security做权限框架的,解决思路就是工具生成token,拦截器或过滤器验证token有效性;还有一些是用了Spring Security权限框架,但是只用token做权限认证,没有使用session的,只需要按照这篇文章去自定义认证,然后用过滤器去验证token。但是这里面最重要的是理清楚Spring Security是这么判断用户已经登录的,使用token怎么让Spring Security去知道当前已登录。这就要了解Spring Security的认证流程了。

三、疑惑

首先,我们都知道,用Spring Security获取当前用户认证的方法 SecurityContextHolder.getContext().getAuthentication(),这里大家有没有思考过,默认情况我们都是用session管理用户登录信息的,通过上面的方法是怎么跟session联系起来的,我们看下SecurityContextHolder跟SpringContext实现类的源码


public class SecurityContextHolder {
	// ~ Static fields/initializers
	// =====================================================================================

	public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL";
	public static final String MODE_INHERITABLETHREADLOCAL = "MODE_INHERITABLETHREADLOCAL";
	public static final String MODE_GLOBAL = "MODE_GLOBAL";
	public static final String SYSTEM_PROPERTY = "spring.security.strategy";
	private static String strategyName = System.getProperty(SYSTEM_PROPERTY);
	private static SecurityContextHolderStrategy strategy;
	private static int initializeCount = 0;

	static {
		initialize();
	}

	// ~ Methods
	// ========================================================================================================

	/**
	 * Explicitly clears the context value from the current thread.
	 */
	public static void clearContext() {
		strategy.clearContext();
	}

	/**
	 * Obtain the current <code>SecurityContext</code>.
	 *
	 * @return the security context (never <code>null</code>)
	 */
	public static SecurityContext getContext() {
		return strategy.getContext();
	}

	/**
	 * Associates a new <code>SecurityContext</code> with the current thread of execution.
	 *
	 * @param context the new <code>SecurityContext</code> (may not be <code>null</code>)
	 */
	public static void setContext(SecurityContext context) {
		strategy.setContext(context);
	}

	
}

public class SecurityContextImpl implements SecurityContext {

	private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

	// ~ Instance fields
	// ================================================================================================

	private Authentication authentication;

	public SecurityContextImpl() {}

	public SecurityContextImpl(Authentication authentication) {
		this.authentication = authentication;
	}

	// ~ Methods
	// ========================================================================================================

	@Override
	public boolean equals(Object obj) {
		if (obj instanceof SecurityContextImpl) {
			SecurityContextImpl test = (SecurityContextImpl) obj;

			if ((this.getAuthentication() == null) && (test.getAuthentication() == null)) {
				return true;
			}

			if ((this.getAuthentication() != null) && (test.getAuthentication() != null)
					&& this.getAuthentication().equals(test.getAuthentication())) {
				return true;
			}
		}

		return false;
	}

	@Override
	public Authentication getAuthentication() {
		return authentication;
	}

	@Override
	public int hashCode() {
		if (this.authentication == null) {
			return -1;
		}
		else {
			return this.authentication.hashCode();
		}
	}

	@Override
	public void setAuthentication(Authentication authentication) {
		this.authentication = authentication;
	}

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append(super.toString());

		if (this.authentication == null) {
			sb.append(": Null authentication");
		}
		else {
			sb.append(": Authentication: ").append(this.authentication);
		}

		return sb.toString();
	}
}

我们只看重点部分,SpringContext是通过 SecurityContextHolderStrategy 取出来的,而Authentication对象是它的一个属性,这里看起来也没跟session有什么关联,看来重点应该在 SecurityContextHolderStrategy 里了,我们找到了它的一个实现ThreadLocalSecurityContextHolderStrategy


final class ThreadLocalSecurityContextHolderStrategy implements
		SecurityContextHolderStrategy {
	// ~ Static fields/initializers
	// =====================================================================================

	private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<>();

	// ~ Methods
	// ========================================================================================================

	public void clearContext() {
		contextHolder.remove();
	}

	public SecurityContext getContext() {
		SecurityContext ctx = contextHolder.get();

		if (ctx == null) {
			ctx = createEmptyContext();
			contextHolder.set(ctx);
		}

		return ctx;
	}

	public void setContext(SecurityContext context) {
		Assert.notNull(context, "Only non-null SecurityContext instances are permitted");
		contextHolder.set(context);
	}

	public SecurityContext createEmptyContext() {
		return new SecurityContextImpl();
	}
}

看出来是从线程局部变量里获取的,但是是什么时候放进去的呢?查找了Spring Security的资料后,找到了一个拦截SecurityContextPersistenceFilter

public class SecurityContextPersistenceFilter extends GenericFilterBean {

   static final String FILTER_APPLIED = "__spring_security_scpf_applied";
   //安全上下文存储的仓库
   private SecurityContextRepository repo;

   public SecurityContextPersistenceFilter() {
      //HttpSessionSecurityContextRepository是SecurityContextRepository接口的一个实现类
      //使用HttpSession来存储SecurityContext
      this(new HttpSessionSecurityContextRepository());
   }

   public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
         throws IOException, ServletException {
      HttpServletRequest request = (HttpServletRequest) req;
      HttpServletResponse response = (HttpServletResponse) res;

      if (request.getAttribute(FILTER_APPLIED) != null) {
         // ensure that filter is only applied once per request
         chain.doFilter(request, response);
         return;
      }
      request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
      //包装request,response
      HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request,
            response);
      //从Session中获取安全上下文信息
      SecurityContext contextBeforeChainExecution = repo.loadContext(holder);
      try {
         //请求开始时,设置安全上下文信息,这样就避免了用户直接从Session中获取安全上下文信息
         SecurityContextHolder.setContext(contextBeforeChainExecution);
         chain.doFilter(holder.getRequest(), holder.getResponse());
      }
      finally {
         //请求结束后,清空安全上下文信息
         SecurityContext contextAfterChainExecution = SecurityContextHolder
               .getContext();
         SecurityContextHolder.clearContext();
         repo.saveContext(contextAfterChainExecution, holder.getRequest(),
               holder.getResponse());
         request.removeAttribute(FILTER_APPLIED);
         if (debug) {
            logger.debug("SecurityContextHolder now cleared, as request processing completed");
         }
      }
   }

}

 每次请求过来都会先进入这个拦截器,然后通过HttpSessionSecurityContextRepository来获取SpringContext上下文,而SpringContext则是放在session里面的,结束的时候又将SpringContext放回session,此处终于找到关联session的地方。

public class HttpSessionSecurityContextRepository implements SecurityContextRepository {
   // 'SPRING_SECURITY_CONTEXT'是安全上下文默认存储在Session中的键值
   public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT";
   ...
   private final Object contextObject = SecurityContextHolder.createEmptyContext();
   private boolean allowSessionCreation = true;
   private boolean disableUrlRewriting = false;
   private String springSecurityContextKey = SPRING_SECURITY_CONTEXT_KEY;

   private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();

   //从当前request中取出安全上下文,如果session为空,则会返回一个新的安全上下文
   public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
      HttpServletRequest request = requestResponseHolder.getRequest();
      HttpServletResponse response = requestResponseHolder.getResponse();
      HttpSession httpSession = request.getSession(false);
      SecurityContext context = readSecurityContextFromSession(httpSession);
      if (context == null) {
         context = generateNewContext();
      }
      ...
      return context;
   }

   ...

   public boolean containsContext(HttpServletRequest request) {
      HttpSession session = request.getSession(false);
      if (session == null) {
         return false;
      }
      return session.getAttribute(springSecurityContextKey) != null;
   }

   private SecurityContext readSecurityContextFromSession(HttpSession httpSession) {
      if (httpSession == null) {
         return null;
      }
      ...
      // Session存在的情况下,尝试获取其中的SecurityContext
      Object contextFromSession = httpSession.getAttribute(springSecurityContextKey);
      if (contextFromSession == null) {
         return null;
      }
      ...
      return (SecurityContext) contextFromSession;
   }

   //初次请求时创建一个新的SecurityContext实例
   protected SecurityContext generateNewContext() {
      return SecurityContextHolder.createEmptyContext();
   }

}

四、如何验证是否登录

FilterSecurityInterceptor此拦截器是用来判断用户是否登录以及有哪些资源的权限的,这个拦截器最后会找到你配置的未登录表单路径,重定向到该路径,这个我会单独拿出来讲一下。

 

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/111316.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • Hadoop里的Partitioner

    Hadoop里的Partitioner

  • C#之 对象数组

    C#之 对象数组对象数组就是数组里的每个元素都是类的对象,赋值时先定义对象,然后将对象直接赋给数组就行了。万物皆可对象,举个例子:一台电脑。我们就可以把电脑看成一个对象。第一种:常规的写法string[]xxx={}例如我们写一个名字集合的数组:string[]name=newstring[]{“小白”,”小黑”,”小明”};//可以简写为:tring[]nam…

  • tomcat的配置

    tomcat的配置tomcat的配置

  • Java教学视频下载(全集)

    Java教学视频下载(全集)1:四百本Java电子书下载链接:http://www.verycd.com/topics/2784992/2:蓝点在线Java视频教程http://you.video.sina.com.cn/a/1905723-1393828897/1/2.html3:J2EEWeb程序开发定向班V1.0Java就业班V2.0(完整版)http://www.verycd.com/topics/…

  • bigdecimal除法运算保留两位小数_bigdecimal保留两位小数显示00

    bigdecimal除法运算保留两位小数_bigdecimal保留两位小数显示00**问题:**将两个long类型的数相除后转换为BigDecimal类型并保留两位小数。思路:1.先将long转换为double类型再相除2.相除之后再转换为BigDecimal类型3.最后是设置小数位数,并设置两位小数后面的数的处置方式。代码:longnum1=5L;longnum2=20L;BigDecimalnum=BigDecimal.valueOf((double)num1/num2).setScale(2,BigDecimal.ROUND_HALF_UP);

  • smartsvn 用法

    smartsvn 用法都说SMARTSVN是最全的Mac上的SVN客户端工具,分Pro版和基础版,基础版跟Versions差不多,这里找了Pro版下载并激活成功教程:mac版本smartSVN客户端下载:http://www.s

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号