Springboot Shiro 통합 JWT 구현 예시 코드

12979 단어 SpringbootShiroJWT
앞 에 쓰다
그 전에 JWT 와 Shiro 를 결합 시 키 려 고 했 지만 인터넷 에서 블 로 그 를 찾 아 봤 는데 잘 모 르 기 때문에 Shiro 의 업무 체 제 를 다시 연구 한 다음 에 JWT 와 Shiro 를 통합 시 키 는 방법 을 생각 했다.
그리고 그 다음 에 JWT 와 관련 된 내용 도 언급 할 것 입 니 다.저 는 예전 에 블 로 그 를 한 편 쓴 적 이 있 습 니 다.여 기 를 볼 수 있 습 니 다Springboot JWT 인증 실현
Shiro 의 Session 메커니즘
제 방법 은 Shiro 의 기본 적 인 Session 체 제 를 바 꾸 었 기 때문에 여기 서 Shiro 의 체 제 를 간단하게 설명 하고 Shiro 가 매번 방문 하 는 사용자 가 어떤 사용자 인지 간단하게 확인 합 니 다.
Servlet 의 세 션 메커니즘
Shiro 가 자바 웹 에서 사용 하 는 것 은 기본 Servlet 의 Session 메커니즘 입 니 다.대체적으로 다음 과 같 습 니 다.
image-20200227163238464
1.사용자 최초 요청
2.서버 가 요청 을 받 은 후 자원 에 접근 할 수 있 는 권한 이 있 든 없 든 응답 을 되 돌 릴 때 서버 는 세 션 을 생 성하 여 사용자 의 정 보 를 저장 한 다음 세 션 Id 를 생 성 합 니 다.
3.서버 는 응답 중 jsessionId 라 는 이름 으로 이 SessionId 를 쿠키 로 고객 에 게 보 냅 니 다(바로 Set-Cookie 응답 헤드)
4.쿠키 가 설정 되 어 있 기 때문에 다음 에 방문 할 때 서버 에서 이 SessionId 를 자동 으로 인식 하고 마지막 에 대응 하 는 Session 을 찾 습 니 다.
Shiro 가 가 져 온 변화
그리고 Shiro 와 결합 하면 위의 두 번 째 단계 와 세 번 째 단 계 는 작은 변화 가 발생 합 니 다.
2.>서버 는 세 션 을 만 들 뿐만 아니 라 Subject 대상(Shiro 에서 현재 사용 자 를 대표 하 는 클래스)도 만 들 고 이 SessionId 를 Key 로 연결 합 니 다.
3.>두 번 째 요청 을 받 아들 일 때 Shiro 는 요청 헤더 에서 SessionId 를 찾 아 해당 하 는 Subject 를 찾 아 현재 컨 텍스트 에 연결 합 니 다.이 럴 때 Shiro 는 방문 자가 누 군지 알 수 있 습 니 다.
나의 생각
이것 은 내 가 생각해 낸 것 이기 때문에,어느 정도 문제 가 있 을 수 있 으 니,큰 사람 에 게 지적 해 주 십시오.
주요 사상 은 JWT Token Shiro Sessionimage-20200227151055913
작업 흐름:
사용자 로그 인
  • 성공 하면 shiro 는 현재 Subject 대상 과 일치 하 는 SessionId 를 기본적으로 생 성 합 니 다.이 SessionId 를 JWT 에 넣 습 니 다
  • JWT 로 돌아 가기
  • 사용자 가 두 번 째 로 JWT 를 가지 고 인 터 페 이 스 를 방문 합 니 다서버 분석 JWT,SessionId 획득서버 가 SessionId 를 Shiro 에 넘 겨 관련 인증 을 수행 합 니 다코드 구현
    JWT 패키지 가 져 오기
    가 져 오기java-jwt가방:
    이 가방 에는 일련의 jwt 작업 api 가 구현 되 어 있 습 니 다.
    만약 당신 이 Maven 유저 라면:
    pom.xml 에 쓰기
    
    <!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
    <dependency>
      <groupId>com.auth0</groupId>
      <artifactId>java-jwt</artifactId>
      <version>3.8.3</version>
    </dependency>
    만약 당신 이 Gradle 유저 라면:
    build.gradle 에 쓰기
    
    compile group: 'com.auth0', name: 'java-jwt', version: '3.8.3'
    만약 당신 이 다른 유저 라면:
    maven 중앙 창고 주소여기,이곳
    JWT 도구 클래스
    JwtUtils,코드 는 다음 과 같 습 니 다.
    
    import com.auth0.jwt.JWT;
    import com.auth0.jwt.JWTVerifier;
    import com.auth0.jwt.algorithms.Algorithm;
    import com.auth0.jwt.exceptions.JWTDecodeException;
    import com.auth0.jwt.interfaces.Claim;
    import com.auth0.jwt.interfaces.DecodedJWT;
    
    import java.io.Serializable;
    import java.util.Calendar;
    import java.util.Date;
    
    /**
     * @author Lehr
     * @create: 2020-02-04
     */
    public class JwtUtils {
    
      /**
           :     id
           :  
           :30  
           :     :      ,      
           :    id       
       */
      public static String createToken(String userId,String realName, String userName) {
    
        Calendar nowTime = Calendar.getInstance();
        nowTime.add(Calendar.MINUTE,30);
        Date expiresDate = nowTime.getTime();
    
        return JWT.create().withAudience(userId)  //    
            .withIssuedAt(new Date())  //    
            .withExpiresAt(expiresDate) //    
            .withClaim("userName", userName)  //  ,        
            .withClaim("realName", realName)
            .sign(Algorithm.HMAC256(userId+"HelloLehr"));  //  
      }
    
      /**
       *      ,  secret            id
       * @param token
       * @throws TokenUnavailable
       */
      public static void verifyToken(String token, String secret) throws TokenUnavailable {
        DecodedJWT jwt = null;
        try {
          JWTVerifier verifier = JWT.require(Algorithm.HMAC256(secret+"HelloLehr")).build();
          jwt = verifier.verify(token);
        } catch (Exception e) {
          //    
          //                 ,        
          throw new TokenUnavailable();
        }
      }
    
      /**
      *       
      */
      public static String getAudience(String token) throws TokenUnavailable {
        String audience = null;
        try {
          audience = JWT.decode(token).getAudience().get(0);
        } catch (JWTDecodeException j) {
          //   token    
          throw new TokenUnavailable();
        }
        return audience;
      }
    
    
      /**
      *             
      */
      public static Claim getClaimByName(String token, String name){
        return JWT.decode(token).getClaim(name);
      }
    }
    
    작은 설명:
    jwt 생 성 시 암호 화 및 검증 방법:
    jwt 의 검증 은 사실 jwt 의 마지막 부분(서명 부분)을 검증 하 는 것 이다.여기에 서명 한 암호 화 방식 을 지정 할 때 암호 화 된 문자열 도 들 어 왔 기 때문에 검증 할 때 암호 화 알고리즘 을 알 아야 할 뿐만 아니 라 이 문자열 을 얻어 야 복호화 에 성공 하고 안전성 을 높 일 수 있 습 니 다.제 가 사용 하 는 것 은 id 입 니 다.비교적 간단 합 니 다.만약 에 더 안전 하고 싶다 면 사용자 비밀 번 호 를 이 암호 화 문자열 로 사용 할 수 있 습 니 다.그러면 이 업무 코드 가 누설 되 더 라 도 큰 안전 문 제 를 일 으 키 지 않 을 것 입 니 다.(제 id 는 누구나 알 고 있 기 때문에 토 큰 은 위 조 될 수 있 습 니 다.그러나 비밀번호 로 바 꾸 면 데이터 베이스 가 무사 하면 아무 도 모 릅 니 다)
    부하 획득 방법:
    누 군 가 는 이상 하 게 생각 할 수 있 습 니 다.왜 복호화 가 필요 하지 않 고 verify 없 이 부하 에 있 는 내용 을 얻 을 수 있 습 니까?이 유 는 원래 부하 가 Base 64 로 처리 되 었 을 뿐 암호 성 이 없 기 때문에 그 값 을 직접 얻 을 수 있 습 니 다.그러나 이 값 의 진실성 을 믿 을 수 있 는 지 없 는 지 는 검증 을 통과 할 수 있 는 지 를 봐 야 합 니 다.마지막 서명 부분 은 앞 머리 와 부하 내용 과 관련 이 있 기 때문에 서명 이 검증 되면그럼 앞의 부하 가 바 뀌 지 않 았 다 는 거 야.
    컨트롤 러 층
     로그 인 논리
    
      /**
       *     
       * @param userName
       * @param password
       * @param req
       * @return
       * @throws Exception
       */
      @SneakyThrows
      @PostMapping(value = "/login")
      public AccountVO login(String userName, String password, HttpServletRequest req){
        //    
        Subject subject = SecurityUtils.getSubject();
        try {
          subject.login(new UsernamePasswordToken(userName, password));
        } catch (Exception e) {
          throw new LoginFailed();
        }
        AccountVO account = accountService.getAccountByUserName(userName);
        String id = account.getId();
        //  jwtToken
        String jwtToken = JwtUtils.createToken(id, account.getRealName(),account.getUserName(), subject.getSession().getId().toString());
        //   token,                
        req.setAttribute("token", jwtToken);
        return account;
      }
    주로 로그 인 에 성공 한 후 이 Subject 의 SessionId 를 JWT 에 넣 고 token 을 생 성 합 니 다.
    
    String jwtToken = JwtUtils.createToken(id,account.getRealName(),account.getUserName(),subject.getSession().getId().toString());
    이후 에 우 리 는 JWT 분석 을 통 해 SessionId 를 얻 을 수 있 습 니 다.매번 SessionId 를 쿠키 로 되 돌려 주 는 것 이 아 닙 니 다.
    종료 논리
    우선 JWT 토 큰 자체 가 효력 을 잃 기 때문에 JWT 토 큰 이 효력 을 잃 으 면 탈퇴 하 는 셈 이다.
    그리고 우 리 는 Shiro 의 전통 적 인 수 동 로그 인 을 실현 할 수 있 습 니 다.
    
    public String logout(HttpServletRequest req) {
        SecurityUtils.getSubject().logout();
        return "       ";
      }
    이렇게 되면 Realm 의 사용자 상 태 는 인증 되 지 않 은 상태 가 되 고 JWT 가 기한 이 지나 지 않 아 도 다시 로그 인 해 야 합 니 다.
    사용자 정의 SessionManager
    선행 코드:
    
    package com.imlehr.internship.shiroJwt;
    
    import com.imlehr.internship.exception.TokenUnavailable;
    import lombok.SneakyThrows;
    import org.apache.shiro.session.mgt.SessionKey;
    import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
    import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
    import org.apache.shiro.web.util.WebUtils;
    
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.Serializable;
    import java.util.UUID;
    
    /**
     * @author Lehr
     * @create: 2020-02-10
     */
    public class CustomSessionManager extends DefaultWebSessionManager {
    
    
      //         lombok   
      @SneakyThrows
      @Override
      protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
    
        String token = WebUtils.toHttp(request).getHeader("token");
        System.out.println("        token :" + token);
        if (token == null || token.length()<1) {
          return UUID.randomUUID().toString();
        }
    
        //       jwt ,         
        String userId = JwtUtils.getAudience(token);
        JwtUtils.verifyToken(token, userId);
        String sessionId = JwtUtils.getClaimByName(token, "sessionId").asString();
    
        if (sessionId == null) {
          return new TokenUnavailable();
        }
    
        
        request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "header");
        request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, token);
        request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
        request.setAttribute(ShiroHttpServletRequest.SESSION_ID_URL_REWRITING_ENABLED, isSessionIdUrlRewritingEnabled());
    
        return sessionId;
      }
    
    
    }
    
    이전 세 션 의 획득 은 Default WebSession Manager 에서 이 루어 졌 기 때문에 우 리 는 지금 이 종 류 를 다시 쓰 고 세 션 의 논 리 를 어떻게 얻 는 지 쓰 면 됩 니 다.
    여기 서 두 가지 방법 을 말 합 니 다.
    getSessionId(SessionKey key)
    이 방법 은 Default WebSession Manager 에 있 습 니 다.여 기 는 다시 쓰 지 않 았 습 니 다.우리 위 에 다시 쓰 는 것 은 뒤의 두 번 째 동명 방법 입 니 다.다만 여기 서 이야기 하고 싶 을 뿐 독자 가 이 부분 을 직접 뛰 어 넘 어도 영향 을 주지 않 습 니 다.
    소스 코드 논리
    Shiro 가 SessionId 를 가 져 오 려 고 할 때 먼저 이 방법 을 사용 합 니 다.httpRequest 에 들 어 오 는 방법 이 아 닙 니 다.
    Default WebSession Manager 에서 그 는 이렇게 했다.
    
    @Override
    public Serializable getSessionId(SessionKey key) {
      Serializable id = super.getSessionId(key);
      if (id == null && WebUtils.isWeb(key)) {
        ServletRequest request = WebUtils.getRequest(key);
        ServletResponse response = WebUtils.getResponse(key);
        //         
        id = getSessionId(request, response);
      }
      return id;
    }
  • id 를 찾 지 못 하면 두 번 째 동명 이인 방법 을 사용 합 니 다
  • 있 으 면 돌아 와여기 서 주의해 야 할 것 은 이 방법 은 전체 검증 과정 에서 여러 번 반복 적 으로 호출 되 고 서버 가 사용자 의 요청 을 받 아들 일 때 한 번 만 호출 되 는 방법 은 다음 과 같 습 니 다.즉,우리 가 다시 쓴 것 입 니 다.
    getSessionId(ServletRequest request, ServletResponse response)
    이것 이 야 말로 서버 가 요청 을 받 아들 일 때 Session 논 리 를 가 져 오고 사용자 의 요청 메시지 에서 SessionId 를 가 져 오 는 것 입 니 다.
    그래서 저희 가 다시 써 야 할 게 이 거 였 어 요.
    원본 의 논 리 는 쿠키 에서 sessionId 의 값 을 찾 는 것 입 니 다.
    우 리 는 Header 에서 JWT(즉 요청 헤더 의'token'헤더 에서 찾 는 것)를 찾 은 다음 JWT 를 분석 하여 우리 가 저장 하고 있 는 SessionId 속성 을 얻 으 면 된다.
    ShiroConfiguration
    저 희 는 자기가 쓴 Session Manager 만 설정 하면 돼 요.
    우선 배합:
    
    public DefaultWebSessionManager sessionManager()
    {
      CustomSessionManager customSessionManager = new CustomSessionManager();
      return customSessionManager;
    }
    그리고 Security Manager 를 넣 습 니 다.
    
    public SecurityManager securityManager(MyRealm myRealm) {
      
      DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
      securityManager.setRealm(myRealm);
      
      return securityManager;
    }
    완성 하 다.🎉
    테스트
    로그 인
    image-20200227161124575
    저희 가 JWT 를 얻 었 는데 JWT 에 Session Id 가 들 어 있어 요.
    추가 요청 token 없 음
    image-20200227161223110
    분명히 인증 을 하지 않 았 습 니 다.우 리 는 백 스테이지 를 보 겠 습 니 다.
    image-20200227161319858
    token 을 얻 을 수 없 기 때문에 이 사용자 가 대응 하 는 sessionId 를 얻 을 수 없 기 때문에 차단 권한 을 수 여 받 았 습 니 다.
    뒤에 있 는 JSESSIONID 는 sessionId 가 새로 생 성 되 지 않 았 기 때문에 로그 인하 지 않 은 사용자 에 게 대응 하면 자 연 스 럽 게 거절당 합 니 다.
    이전 token 을 가 져 가 야 shiro 는 우리 가 이전에 로그 인 한 사용자 라 고 생각 할 수 있 습 니 다.
    추가 요청 token
    image-20200227161551066
    배경:
    image-20200227161616008
    성공!
    또 JWT 자체 가 RESTful API 서비스 에 적합 하기 때문에 시 로 와 레 디 스 를 통합 해 분산 형 으로 만 들 면 효과 가 더 좋다
    Springboot 가 Shiro 통합 JWT 를 실현 하 는 예제 코드 에 관 한 이 글 은 여기까지 소개 되 었 습 니 다.더 많은 Springboot Shiro 통합 JWT 내용 은 우리 의 이전 글 을 검색 하거나 아래 의 관련 글 을 계속 조회 하 시기 바 랍 니 다.앞으로 많은 응원 바 랍 니 다!

    좋은 웹페이지 즐겨찾기