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;
	}
}
相關文章
相關標籤/搜索