springboot整合shiro使用jwt作认证、受权

1.引入shiro、jwt的依赖库
html

	org.apache.shiro
	shiro-web
	1.7.1
	org.apache.shiro
	shiro-spring
	1.7.1
	com.auth0
	java-jwt
	3.16.0

2.添加自定义认证token类,实现shiro的AuthenticationToken接口java

import org.apache.shiro.authc.AuthenticationToken;

public class OAuth2Token implements AuthenticationToken {

	private static final long serialVersionUID = 451903942673942296L;

	private String token;

	public OAuth2Token(String token) {
		this.token = token;
	}

	@Override
	public Object getPrincipal() {
		return token;
	}

	@Override
	public Object getCredentials() {
		return token;
	}
}

3.建立realm类,实现认证、受权的接口web

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.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class OAuth2Realm extends AuthorizingRealm {

	@Autowired
	private JwtUtil jwtUtil;

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

	/**
	 * 受权(验证权限时调用)
	 */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

		SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
		// TODO 查询用户的权限列表
		// TODO 把权限列表添加到info对象中
		return info;
	}

	/**
	 * 认证(登陆时调用)
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		// TODO 从令牌中获取userId,而后检测该帐户是否被冻结。
		SimpleAuthenticationInfo info = new SimpleAuthenticationInfo();
		// TODO 往info对象中添加用户信息、Token字符串
		return info;
	}
}

4.令牌的刷新机制
给令牌设置有效期,并把令牌放到redis中,设置redis中数据的有效期大于令牌的有限期。当校验到令牌过时后,从redis中获取数据,若是能获取到数据,则生成新的令牌对令牌进行续期。
若是redis中没有获取到数据,即redis中的数据也过时了,则用户必须从新登录。redis

5.建立ThreadLocal类,在线程中存储tokenspring

public class ThreadLocalToken {
	private static ThreadLocallocal = new ThreadLocal<>();

	public static void setToken(String token) {
		local.set(token);
	}

	public static String getToken() {
		return (String) local.get();
	}

	public static void clear() {
		local.remove();
	}
}

6.添加AuthenticatingFilter处理类,判断请求是否须要作过滤,以及须要过滤后作令牌的校验和权限的判断apache

import java.io.IOException;
import java.util.concurrent.TimeUnit;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpStatus;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;

import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.TokenExpiredException;

@Component
@SuppressWarnings("rawtypes")
public class OAuth2Filter extends AuthenticatingFilter {

	@Value("${emos.jwt.cache-expire}")
	private int cacheExpire;

	@Autowired
	private JwtUtil jwtUtil;

	@Autowired
	private RedisTemplate redisTemplate;

	/**
	 * 拦截请求以后,用于把令牌字符串封装成令牌对象
	 */
	@Override
	protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
		// 获取请求token
		String token = getRequestToken((HttpServletRequest) request);

		if (StringUtils.isBlank(token)) {
			return null;
		}

		return new OAuth2Token(token);
	}

	/**
	 * 拦截请求,判断请求是否须要被Shiro处理
	 */
	@Override
	protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
		HttpServletRequest req = (HttpServletRequest) request;
		// Ajax提交application/json数据的时候,会先发出Options请求
		// 这里要放行Options请求,不须要Shiro处理
		if (req.getMethod().equals(RequestMethod.OPTIONS.name())) {
			return true;
		}
		// 除了Options请求以外,全部请求都要被Shiro处理
		return false;
	}

	/**
	 * 该方法用于处理全部应该被Shiro处理的请求
	 */
	@SuppressWarnings("unchecked")
	@Override
	protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
		HttpServletRequest req = (HttpServletRequest) request;
		HttpServletResponse resp = (HttpServletResponse) response;

		resp.setHeader("Content-Type", "text/html;charset=UTF-8");
		// 容许跨域请求
		resp.setHeader("Access-Control-Allow-Credentials", "true");
		resp.setHeader("Access-Control-Allow-Origin", req.getHeader("Origin"));

		ThreadLocalToken.clear();
		// 获取请求token,若是token不存在,直接返回401
		String token = getRequestToken((HttpServletRequest) request);
		if (StringUtils.isBlank(token)) {
			resp.setStatus(HttpStatus.SC_UNAUTHORIZED);
			resp.getWriter().print("无效的令牌");
			return false;
		}

		try {
			jwtUtil.verifierToken(token); // 检查令牌是否过时
		} catch (TokenExpiredException e) {
			// 客户端令牌过时,查询Redis中是否存在令牌,若是存在令牌就从新生成一个令牌给客户端
			if (redisTemplate.hasKey(token)) {
				redisTemplate.delete(token);// 删除令牌
				int userId = jwtUtil.getUserId(token);
				token = jwtUtil.createToken(userId); // 生成新的令牌
				// 把新的令牌保存到Redis中
				redisTemplate.opsForValue().set(token, userId + "", cacheExpire, TimeUnit.DAYS);
				// 把新令牌绑定到线程
				ThreadLocalToken.setToken(token);
			} else {
				// 若是Redis不存在令牌,让用户从新登陆
				resp.setStatus(HttpStatus.SC_UNAUTHORIZED);
				resp.getWriter().print("令牌已通过期");
				return false;
			}

		} catch (JWTDecodeException e) {
			resp.setStatus(HttpStatus.SC_UNAUTHORIZED);
			resp.getWriter().print("无效的令牌");
			return false;
		}

		// 登陆和受权校验工做
		boolean bool = executeLogin(request, response);
		return bool;
	}

	/**
	 * 登陆失败后的返回信息
	 */
	@Override
	protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request,
			ServletResponse response) {
		HttpServletRequest req = (HttpServletRequest) request;
		HttpServletResponse resp = (HttpServletResponse) response;
		resp.setStatus(HttpStatus.SC_UNAUTHORIZED);
		resp.setContentType("application/json;charset=utf-8");
		resp.setHeader("Access-Control-Allow-Credentials", "true");
		resp.setHeader("Access-Control-Allow-Origin", req.getHeader("Origin"));
		try {
			resp.getWriter().print(e.getMessage());
		} catch (IOException exception) {

		}
		return false;
	}

	/**
	 * 获取请求头里面的token
	 */
	private String getRequestToken(HttpServletRequest httpRequest) {
		// 从header中获取token
		String token = httpRequest.getHeader("token");

		// 若是header中不存在token,则从参数中获取token
		if (StringUtils.isBlank(token)) {
			token = httpRequest.getParameter("token");
		}
		return token;
	}

}

7.建立配置类ShiroConfigjson

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

import javax.servlet.Filter;

import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ShiroConfig {

	@Bean("securityManager")
	public SecurityManager securityManager(OAuth2Realm oAuth2Realm) {
		DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
		securityManager.setRealm(oAuth2Realm);
		securityManager.setRememberMeManager(null);
		return securityManager;
	}

	@Bean("shiroFilter")
	public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager, OAuth2Filter oAuth2Filter) {
		ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
		shiroFilter.setSecurityManager(securityManager);

		// oauth过滤
		Mapfilters = new HashMap<>();
		filters.put("oauth2", oAuth2Filter);
		shiroFilter.setFilters(filters);

		MapfilterMap = new LinkedHashMap<>();
		filterMap.put("/webjars/**", "anon");
		filterMap.put("/druid/**", "anon");
		filterMap.put("/app/**", "anon");
		filterMap.put("/sys/login", "anon");
		filterMap.put("/swagger/**", "anon");
		filterMap.put("/v2/api-docs", "anon");
		filterMap.put("/swagger-ui.html", "anon");
		filterMap.put("/swagger-resources/**", "anon");
		filterMap.put("/captcha.jpg", "anon");
		filterMap.put("/user/register", "anon");
		filterMap.put("/user/login", "anon");
		filterMap.put("/test/**", "anon");
		filterMap.put("/**", "oauth2");
		shiroFilter.setFilterChainDefinitionMap(filterMap);
		return shiroFilter;
	}
	
	@Bean("lifecycleBeanPostProcessor")
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }
	
	@Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }
}

8.利用AOP把新的token返回给客户端api

import javax.servlet.http.HttpServletResponse;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.example.emos.wx.config.shiro.ThreadLocalToken;

@Aspect
@Component
public class TokenAspect {

	@Pointcut("execution(public * com.example.emos.wx.controller.*.*(..)))")
	public void aspect() {

	}

	@Around("aspect()")
	public Object around(ProceedingJoinPoint point) throws Throwable {
		Object result = point.proceed(); // 方法执行结果
		String token = ThreadLocalToken.getToken();
		// 若是ThreadLocal中存在Token,说明是更新的Token
		if (token != null) {
			HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
			response.setHeader("token", token); // 往响应中放置Token
			ThreadLocalToken.clear();
		}
		return result;
	}
}
相关文章
相关标签/搜索