當前位置:
首頁 >
前端技术
> javascript
>内容正文
javascript
学习Spring Boot:(十六)使用Shiro与JWT 实现认证服务
生活随笔
收集整理的這篇文章主要介紹了
学习Spring Boot:(十六)使用Shiro与JWT 实现认证服务
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
前言
代碼可以參考
需要把Web應用做成無狀態的,即服務器端無狀態,就是說服務器端不會存儲像會話這種東西,而是每次請求時access_token進行資源訪問。這里我們將使用 JWT 1,基于散列的消息認證碼,使用一個密鑰和一個消息作為輸入,生成它們的消息摘要。該密鑰只有服務端知道。訪問時使用該消息摘要進行傳播,服務端然后對該消息摘要進行驗證。
認證步驟
自定義 jwt 攔截器
/*** oauth2攔截器,現在改為 JWT 認證*/ public class OAuth2Filter extends FormAuthenticationFilter {/*** 設置 request 的鍵,用來保存 認證的 userID,*/private final static String USER_ID = "USER_ID";@Resourceprivate JwtUtils jwtUtils;/*** logger*/private static final Logger LOGGER = LoggerFactory.getLogger(OAuth2Filter.class);/*** shiro權限攔截核心方法 返回true允許訪問resource,** @param request* @param response* @param mappedValue* @return*/@Overrideprotected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {String token = getRequestToken((HttpServletRequest) request);try {// 檢查 token 有效性//ExpiredJwtException JWT已過期//SignatureException JWT可能被篡改Jwts.parser().setSigningKey(jwtUtils.getSecret()).parseClaimsJws(token).getBody();} catch (Exception e) {// 身份驗證失敗,返回 false 將進入onAccessDenied 判斷是否登陸。onLoginFail(response);return false;}Long userId = getUserIdFromToken(token);// 存入到 request 中,在后面的業務處理中可以使用request.setAttribute(USER_ID, userId);return true;}/*** 當訪問拒絕時是否已經處理了;* 如果返回true表示需要繼續處理;* 如果返回false表示該攔截器實例已經處理完成了,將直接返回即可。** @param request* @param response* @return* @throws Exception*/@Overrideprotected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {if (isLoginRequest(request, response)) {if (isLoginSubmission(request, response)) {return executeLogin(request, response);} else {return true;}} else {onLoginFail(response);return false;}}/*** 鑒定失敗,返回錯誤信息* @param token* @param e* @param request* @param response* @return*/@Overrideprotected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {try {((HttpServletResponse) response).setStatus(HttpStatus.BAD_REQUEST.value());response.getWriter().print("賬號活密碼錯誤");} catch (IOException e1) {LOGGER.error(e1.getMessage(), e1);}return false;}/*** token 認證失敗** @param response*/private void onLoginFail(ServletResponse response) {HttpServletResponse httpResponse = (HttpServletResponse) response;((HttpServletResponse) response).setStatus(HttpStatus.UNAUTHORIZED.value());try {response.getWriter().print("沒有權限,請聯系管理員授權");} catch (IOException e) {LOGGER.error(e.getMessage(), e);}}/*** 獲取請求的token*/private String getRequestToken(HttpServletRequest httpRequest) {//從header中獲取tokenString token = httpRequest.getHeader(jwtUtils.getHeader());//如果header中不存在token,則從參數中獲取tokenif (StringUtils.isBlank(token)) {return httpRequest.getParameter(jwtUtils.getHeader());}if (StringUtils.isBlank(token)) {// 從 cookie 獲取 tokenCookie[] cookies = httpRequest.getCookies();if (null == cookies || cookies.length == 0) {return null;}for (Cookie cookie : cookies) {if (cookie.getName().equals(jwtUtils.getHeader())) {token = cookie.getValue();break;}}}return token;}/*** 根據 token 獲取 userID** @param token token* @return userId*/private Long getUserIdFromToken(String token) {if (StringUtils.isBlank(token)) {throw new KCException("無效 token", HttpStatus.UNAUTHORIZED.value());}Claims claims = jwtUtils.getClaimByToken(token);if (claims == null || jwtUtils.isTokenExpired(claims.getExpiration())) {throw new KCException(jwtUtils.getHeader() + "失效,請重新登錄", HttpStatus.UNAUTHORIZED.value());}return Long.parseLong(claims.getSubject());}}將自定義shiro攔截器,設置到 ShiroFilterFactoryBean 中,然后將需要進行權限驗證的 path 進行設置攔截過濾。
登陸
@PostMapping("/login")@ApiOperation("系統登陸")public ResponseEntity<String> login(@RequestBody SysUserLoginForm userForm) {String kaptcha = ShiroUtils.getKaptcha(Constants.KAPTCHA_SESSION_KEY);if (!userForm.getCaptcha().equalsIgnoreCase(kaptcha)) {throw new KCException("驗證碼不正確!");}UsernamePasswordToken token = new UsernamePasswordToken(userForm.getUsername(), userForm.getPassword());Subject currentUser = SecurityUtils.getSubject();currentUser.login(token);//賬號鎖定if (getUser().getStatus() == SysConstant.SysUserStatus.LOCK) {throw new KCException("賬號已被鎖定,請聯系管理員");}// 登陸成功后直接返回 token ,然后后續放到 header 中認證return ResponseEntity.status(HttpStatus.OK).body(jwtUtils.generateToken(getUserId()));}JwtUtils
我前面給 jwt 設置了三個參數
# jwt 配置 jwt:# 加密密鑰secret: 61D73234C4F93E03074D74D74D1E39D9 #blog.wuwii.com# token有效時長expire: 7 # 7天,單位天# token 存在 header 中的參數header: tokenjwt 工具類的編寫
@ConfigurationProperties(prefix = "jwt") @Component public class JwtUtils {/*** logger*/private Logger logger = LoggerFactory.getLogger(JwtUtils.class);/*** 密鑰*/private String secret;/*** 有效期限*/private int expire;/*** 存儲 token*/private String header;/*** 生成jwt token** @param userId 用戶ID* @return token*/public String generateToken(long userId) {Date nowDate = new Date();return Jwts.builder().setHeaderParam("typ", "JWT")// 后續獲取 subject 是 userid.setSubject(userId + "").setIssuedAt(nowDate).setExpiration(DateUtils.addDays(nowDate, expire))// 這里我采用的是 HS512 算法.signWith(SignatureAlgorithm.HS512, secret).compact();}/*** 解析 token,* 利用 jjwt 提供的parser傳入秘鑰,** @param token token* @return 數據聲明 Map<String, Object>*/public Claims getClaimByToken(String token) {try {return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();} catch (Exception e) {return null;}}/*** token是否過期** @return true:過期*/public boolean isTokenExpired(Date expiration) {return expiration.before(new Date());}public String getSecret() {return secret;}public void setSecret(String secret) {this.secret = secret;}public int getExpire() {return expire;}public void setExpire(int expire) {this.expire = expire;}public String getHeader() {return header;}public void setHeader(String header) {this.header = header;} }總結
由于 JWT 這種方式,服務端不需要保存任何狀態,所以服務端不需要使用 session 保存用戶信息,單元測試也比較方便,雖然中間轉碼解碼會消耗一些性能,但是影響不大,還比較方便的應用在 SSO (Single Sign On )。
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎
總結
以上是生活随笔為你收集整理的学习Spring Boot:(十六)使用Shiro与JWT 实现认证服务的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: access开发精要(6)-计算
- 下一篇: erlang精要(4)-列表及运算