스프링 시큐리티의 필터체인이다.
이것이 스프링 시큐리티의 동작 원리를 한 그림으로 나타낸것입니다.
처음보면 참 어지럽다..
여기서 주목해야하는 부분은 Authentication(인증) / Authorization (인가)
https://github.com/codingspecialist/Springboot-Security-JWT-Easy
예제 코드는 여기서 따왔다.
Security Config
하나씩 살펴보겠다.
addFilter ( ) : cors설정 => 모든 cors 통과
csrf().disable() => csrf 토큰 비활성화
이 두 옵션은 이 포스트에 정리를 해놨다. 모르는 지식이면 보고오면 좋다.
https://taehoung0102.tistory.com/227
session 설정 : sessionCreationPolicy.stateless => 세션을 안쓰겠다는 말
.formLogin.diable : 로그인 폼 비활성화
=> 해당 옵션은 시큐리티가 자동으로 지원하는 로그인폼을 안쓰겠다는말
=> 실제로 사용하면, 로그인 폼 경로, 로그인 처리 경로 등등 잘 써먹을수있다.
.httpBasic().disable: JWT 방식을 사용할것이니 비활성화 (Bearer) 방식
.addFilter (JWT 인증필터) :추후 설명
.addFilter(JWT 인가 필터) : 추후 설명
.authorizeRequests() : 이곳에서 경로에 따라 권한을 체크할수있다.
EX) User만 접근가능 경로, Manage만 접근가능 경로, Admin만 접근가능 경로 ..
JWT 인증 필터
JWT 인증필터이다.
우선 extends를 보면 UsernamePasswordAuthenticationFilter를 상속받는다.
이 필터는 맨 처음 스프링 시큐리티의 필터중 하나이다.
이 Username... 필터는 스프링 시큐리티의 /login 요청을 타고 온다.
이 필터가 실행이되면 , attemptAuth.. 인증 시도 메소드가 실행이된다.
코드를 내려읽어보면, ObjectMapper를 사용하여,LoginRequestDto.class와 request의 username과 password를Key : value로 파싱해서 입력해서 반환해주었다.
여기서부터 JWT 인증 시도를 시작한다.
우선, UsernamePasswordAuthenticationToken을 생성한다.
이 토큰은 JWT 토큰이 아니다.
JWT를 시도하기전에, 요청으로준 유저정보 == DB에 담긴 유저정보 가 같은지를 판별하기위한 토큰이다.
UsernamePasswordAuthenticationToken은 요청으로 준 유저정보 측에 해당한다.
그 후 , AuthenticationManager.authenticate(토큰)으로
요청으로준 유저정보 == DB에 담긴 유저정보 가 같은지를 판별한다.
여기서 같지않으면 UnAuthorized 401 메소드가 발생한다!
그 다음 AttempAuth가 성공적으로 200코드를 뱉으면,
다음과같은 successfulAuthent코드가 일어난다.
여기서 진짜 JWT 코드를 생성한다.
JWT.create() 메소드를통해, 생성할수있다.
withSubject : 말그대로 주체가 될만한 값을 넣어야한다. 즉, 객체를 분별할수있는 고유한 ID가 적합하다.
withExpiresAt: 토큰의 유효기간이다. 보통 현재시각+ 토큰의 기간을 넣는다.
withClaim: 토큰 안에 내가 넣고싶은 요소들을 마음껏 넣을수있다.
sign : 토큰을 만들기위해 일종의 싸인을 해야한다. 서버만 아는 시크릿코드를넣어 암호화 알고리즘을 넣는다.
그 후, JWT의 헤더에 (HEADER_STRING) Authorization 을 넣고 , (TOKEN_PREFIX) Bearer + 토큰을 보낸다.
-----------------------------------------------------------------------------------------------------------------
여기까지가 인증을 통한 JWT 발송 절차다.
JWT를 발송했으면 나중에 JWT를 받아서 그것이 유효한 토큰인지 확인하는 "인가" 작업이 필요하다.
JWT 인가 필터
JWT인가필터는 BasicAuthenticathionFileter를 extends 받는다.
이 필터 역시, 시큐리티 체인에 속하는 필터다.
해당 필터는 권한이 필요한 경로를 접근할때만, 적용이된다. 즉, 권한이 없는 경로는 적용이 안된다.
이 필터를 거치면 다음과 같은 메소드가 발생한다.
doFilterInternal 메소드가 발생한다.
해당 메소드에서, 요청으로 들어온 Authorization 토큰과 , DB에 있는 정보가 일치한지 검증한다.
이것이 일치하면, 시큐리티 세션에 접근하여 값 저장, 일치하지않는다면 그냥 리턴시킨다.
-------------------------------------------------------
부록: JwtTokenProvider
JWT 인가, 인증을할때 필요한 기능들을 JwtTokenProvider에 집약시킨 것이므로,
사용하면 인가. 인증필터코드가 매우 짧아지니 구현해서 쓰는걸 추천한다.
EX) JwtTokenProvider
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
@Component
public class JwtTokenProvider {
@Value("${jwt.secret}")
private String secretKey;
@Value("${jwt.expire}")
private long validityInMilliseconds;
public String createToken(String username, List<String> roles) {
Claims claims = Jwts.claims().setSubject(username);
claims.put("roles", roles);
Date now = new Date();
Date validity = new Date(now.getTime() + validityInMilliseconds);
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(validity)
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
}
public Authentication getAuthentication(String token) {
String username = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject();
List<String> roles = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().get("roles", List.class);
List<GrantedAuthority> authorities = roles.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
User principal = new User(username, "", authorities);
return new UsernamePasswordAuthenticationToken(principal, token, authorities);
}
public boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}
}
|
cs |
EX) Jwt인가 필터
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
public class JwtAuthenticationFilter extends BasicAuthenticationFilter {
private final JwtTokenProvider jwtTokenProvider;
public JwtAuthenticationFilter(AuthenticationManager authenticationManager, JwtTokenProvider jwtTokenProvider) {
super(authenticationManager);
this.jwtTokenProvider = jwtTokenProvider;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String header = request.getHeader("Authorization");
if (header == null || !header.startsWith("Bearer ")) {
chain.doFilter(request, response);
return;
}
String token = header.replace("Bearer ", "");
if (jwtTokenProvider.validateToken(token)) {
Authentication authentication = jwtTokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}
}
|
cs |
'자바 > 스프링(Spring)' 카테고리의 다른 글
myBatis 세팅 및 직접 사용해보기 (0) | 2023.07.12 |
---|---|
Intelij 에서 Mysql테이블 확인 및 쿼리쓰기 (0) | 2023.06.14 |
(Java) 공공데이터 API 가져와서 쓰는법 (0) | 2023.03.29 |
Spring Eureka (스프링 유레카) 간단 실습 및 예제 (0) | 2023.03.28 |
Spring Cloud Gateway 간단실습 및 이해 (0) | 2023.03.27 |