有狀態 vs 無狀態
有狀態
那么Session在何時創建呢?
當然還是在服務器端程序運行的過程中創建的,不同語言實現的應用程序有不同創建Session的方法,而在Java中是通過調用HttpServletRequest的getSession方法(使用true作為參數)創建的。
在創建了Session的同時,服務器會為該Session生成唯一的Session id,而這個Session id在隨后的請求中會被用來重新獲得已經創建的Session;
在Session被創建之后,就可以調用Session相關的方法往Session中增加內容了,而這些內容只會保存在服務器中,發到客戶端的只有Session id;
當客戶端再次發送請求的時候,會將這個Session id帶上,服務器接受到請求之后就會依據Session id找到相應的Session,從而再次使用之。
無狀態
有狀態 vs 無狀態
微服務認證方案
- 微服務認證方案01-處處安全
- 微服務認證方案02-外部無狀態,內部有狀態
- 微服務認證方案03-網關認證授權,內部裸奔
- 微服務認證方案04-內部裸奔改進
微服務認證方案01-處處安全
https://github.com/chengjiansheng/cjs-oauth2-sso-demo.git
微服務認證方案02-外部無狀態,內部有狀態
微服務認證方案03-網關認證授權,內部裸奔
微服務認證方案04-內部裸奔改進
方案對比與選擇
訪問控制模型
JWT是什么?
小程序登錄邏輯
實現登錄狀態檢查
public @interface CheckLogin {
}
import com.itmuch.usercenter.util.JwtOperator;
import io.jsonwebtoken.Claims;
import lombok.RequiredArgsConstructor;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Objects;
@Aspect
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class AuthAspect {
private final JwtOperator jwtOperator;
@Around("@annotation(com.itmuch.usercenter.auth.CheckLogin)")
public Object checkLogin(ProceedingJoinPoint point) throws Throwable {
checkToken();
return point.proceed();
}
private void checkToken() {
try {
// 1. 從header里面獲取token
HttpServletRequest request = getHttpServletRequest();
String token = request.getHeader("X-Token");
// 2. 校驗token是否合法&是否過期;如果不合法或已過期直接拋異常;如果合法放行
Boolean isValid = jwtOperator.validateToken(token);
if (!isValid) {
throw new SecurityException("Token不合法!");
}
// 3. 如果校驗成功,那么就將用戶的信息設置到request的attribute里面
Claims claims = jwtOperator.getClaimsFromToken(token);
request.setAttribute("id", claims.get("id"));
request.setAttribute("wxNickname", claims.get("wxNickname"));
request.setAttribute("role", claims.get("role"));
} catch (Throwable throwable) {
throw new SecurityException("Token不合法");
}
}
private HttpServletRequest getHttpServletRequest() {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes;
return attributes.getRequest();
}
@Around("@annotation(com.itmuch.usercenter.auth.CheckAuthorization)")
public Object checkAuthorization(ProceedingJoinPoint point) throws Throwable {
try {
// 1. 驗證token是否合法;
this.checkToken();
// 2. 驗證用戶角色是否匹配
HttpServletRequest request = getHttpServletRequest();
String role = (String) request.getAttribute("role");
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
CheckAuthorization annotation = method.getAnnotation(CheckAuthorization.class);
String value = annotation.value();
if (!Objects.equals(role, value)) {
throw new SecurityException("用戶無權訪問!");
}
} catch (Throwable throwable) {
throw new SecurityException("用戶無權訪問!", throwable);
}
return point.proceed();
}
}
@GetMapping("/{id}")
@CheckLogin
public User findById(@PathVariable Integer id) {
log.info("我被請求了...");
return this.userService.findById(id);
}
Feign調用時傳遞token
使用攔截器
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
public class TokenRelayRequestIntecepor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
// 1. 獲取到token
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes;
HttpServletRequest request = attributes.getRequest();
String token = request.getHeader("X-Token");
// 2. 將token傳遞
if (StringUtils.isNotBlank(token)) {
template.header("X-Token", token);
}
}
}
RestTemplate傳遞token
RequestInterceptor
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
public class TokenRelayRequestIntecepor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
// 1. 獲取到token
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes;
HttpServletRequest request = attributes.getRequest();
String token = request.getHeader("X-Token");
// 2. 將token傳遞
if (StringUtils.isNotBlank(token)) {
template.header("X-Token", token);
}
}
}
ClientHttpRequestInterceptor
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
public class TestRestTemplateTokenRelayInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes;
HttpServletRequest httpRequest = attributes.getRequest();
String token = httpRequest.getHeader("X-Token");
HttpHeaders headers = request.getHeaders();
headers.add("X-Token", token);
// 保證請求繼續執行
return execution.execute(request, body);
}
}
實現用戶權限驗證
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckAuthorization {
String value();
}
import com.itmuch.usercenter.util.JwtOperator;
import io.jsonwebtoken.Claims;
import lombok.RequiredArgsConstructor;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Objects;
@Aspect
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class AuthAspect {
private final JwtOperator jwtOperator;
@Around("@annotation(com.itmuch.usercenter.auth.CheckLogin)")
public Object checkLogin(ProceedingJoinPoint point) throws Throwable {
checkToken();
return point.proceed();
}
private void checkToken() {
try {
// 1. 從header里面獲取token
HttpServletRequest request = getHttpServletRequest();
String token = request.getHeader("X-Token");
// 2. 校驗token是否合法&是否過期;如果不合法或已過期直接拋異常;如果合法放行
Boolean isValid = jwtOperator.validateToken(token);
if (!isValid) {
throw new SecurityException("Token不合法!");
}
// 3. 如果校驗成功,那么就將用戶的信息設置到request的attribute里面
Claims claims = jwtOperator.getClaimsFromToken(token);
request.setAttribute("id", claims.get("id"));
request.setAttribute("wxNickname", claims.get("wxNickname"));
request.setAttribute("role", claims.get("role"));
} catch (Throwable throwable) {
throw new SecurityException("Token不合法");
}
}
private HttpServletRequest getHttpServletRequest() {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes;
return attributes.getRequest();
}
@Around("@annotation(com.itmuch.usercenter.auth.CheckAuthorization)")
public Object checkAuthorization(ProceedingJoinPoint point) throws Throwable {
try {
// 1. 驗證token是否合法;
this.checkToken();
// 2. 驗證用戶角色是否匹配
HttpServletRequest request = getHttpServletRequest();
String role = (String) request.getAttribute("role");
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
CheckAuthorization annotation = method.getAnnotation(CheckAuthorization.class);
String value = annotation.value();
if (!Objects.equals(role, value)) {
throw new SecurityException("用戶無權訪問!");
}
} catch (Throwable throwable) {
throw new SecurityException("用戶無權訪問!", throwable);
}
return point.proceed();
}
}
@PutMapping("/audit/{id}")
@CheckAuthorization("admin")
public Share auditById(@PathVariable Integer id, @RequestBody ShareAuditDTO auditDTO) {
return this.shareService.auditById(id, auditDTO);
}