일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- programmers
- SseEmitter
- spring
- flask
- jenkins
- javascript
- Hibernate
- Anolog
- python
- 생명주기 콜백
- web
- html
- Java
- real time web
- JPA
- Spring Security
- google oauth
- DI
- WIL
- bean
- server send event
- hanghae99
- session
- Project
- jQuery
- oauth
- cookie
- 항해99
- Stream
- JWT
- Today
- Total
끄적끄적 코딩일지
[Spring] Spring Security + JWT 본문
JWT란?
Json Web Token의 약자로 Json 포맷을 사용하여 사용자의 속성을 저장하는 Claim기반의 Web Token이다.
JWT는 암호화 방식과 type등에 대한 정보가 있는 Header, 사용자에 대한 정보(Claim)와 Token에 대한 정보를 담고있는 PayLoad,
Token을 인코딩하거나 유효성을 검증할 때 쓰는 암호화 코드인 Signatured으로 구성되어 있다.
Spring Security에서 JWT 사용하기
Spring security에서 JWT를 사용하려면 기존의 인증방식대신 Filter을 써서 Jwt 발급, 해석을 해야한다.
전체적인 흐름을 정리하자면
로그인 -> 입력한 Id, PW으로 사용자 조회(database) -> 해당 사용자 정보로 Jwt 발급 -> Jwt를 Cookie에 저장
모든 요청에 대해 -> Cookie 의 Jwt 요청 -> Jwt가 Cookie에 있을경우 해독을 통해 사용자 정보 추출 -> 추출한 정보로 Authentication(인증 객체) 생성 및 SecurityContextHolder에 저장
SecurityContextHolder에 인증객체를 저장하면 @AuthenticationPrincipal이나 Thymeleaf의 sec등을 사용할 수 있다.
이번 글에서 사용할 사용자 Entity는 다음과 같다.
@Getter
@Setter
@Entity
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@Column
private String nickName;
@Column
private String name;
@Column(unique = true)
private String loginId;
@Column
private String loginPw;
}
쿠키를 만들고 조회하는 class
public class CookieService {
public static Cookie createCookie(String cookieName, String value){
Cookie token = new Cookie(cookieName,value);
token.setHttpOnly(true);
token.setMaxAge((int) 2 * 360 * 1000);
token.setPath("/");
return token;
}
public static Cookie deleteCookie(String cookieName){
Cookie token = new Cookie(cookieName,"");
token.setHttpOnly(true);
token.setMaxAge(0);
token.setPath("/");
return token;
}
public static Cookie getCookie(HttpServletRequest req, String cookieName){
final Cookie[] cookies = req.getCookies();
if(cookies==null) return null;
for(Cookie cookie : cookies){
if(cookie.getName().equals(cookieName))
return cookie;
}
return null;
}
// 로그아웃시 해당 cookie를 삭제하도록 하자
public static void resetToken(HttpServletResponse response){
Cookie c = deleteCookie("myJwtToken");
response.addCookie(c);
}
}
Step 1. 라이브러리 추가하기
Jwt 관련 기능을 사용하기 위해 라이브러리부터 추가해야한다
Gradle 사용자라면 build.gradle에 추가
dependencies {
....
implementation 'io.jsonwebtoken:jjwt:0.9.1'
implementation 'org.springframework.boot:spring-boot-starter-security'
....
}
maven 사용자라면 pom.xml에 추가
<dependencies>
....
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
....
</dependencies>
Step 2. CustomUserDetail 만들기
Jwt를 해독해서 Authentication(인증객체)을 만들때 사용할 UserDetail을 정의하면 된다.
@Getter
public class MemberDetail implements UserDetails {
private final Member member;
public MemberDetail(Member member){
this.member = member;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.EMPTY_LIST;
}
@Override
public String getPassword() {
return member.getLoginPw();
}
@Override
public String getUsername() {
return member.getLoginId();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
각 항목에 대한 자세한 설명은 이전글을 참고하자
2022.06.04 - [Spring] - [Spring 기초] Spring Security 사용하기
Step 3. JwtProvider 만들기
사용자 정보로 Jwt를 발급하고, Jwt를 해독해서 사용자 정보를 추출하거나 유효성을 검증하고, Authentication을 만드는등 Jwt관련 기능을 모아서 하나의 class으로 만들어서 Bean으로 등록하도록 하자.
@Component
public class JwtTokenProvider {
private final String JWT_SECRET = Base64.getEncoder().encodeToString("비밀 키값".getBytes()); //Jwt암호화와 해독에 필요한 키값 정의
private final long ValidTime = 1000L * 60 * 60; // 토큰의 유효시간 정의(millisec)
private MemberRepo repo; // 사용자 정보 repository
@Autowired
public JwtTokenProvider(MemberRepo repo){
this.repo = repo;
}
// 사용자 정보를 Payload에 담기 + 토큰 발급
public String generateToken(Member m) {
Map<String, Object> claims = new HashMap<>();
claims.put("loginId",m.getLoginId());
claims.put("loginPw",m.getLoginPw());
claims.put("id",m.getId());
return doGenerateToken(claims, "id");
}
// 토큰 발급 로직
private String doGenerateToken(Map<String, Object> claims, String subject) {
return Jwts.builder()
.setHeaderParam("typ","JWT")
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + ValidTime))
.signWith(SignatureAlgorithm.HS256, JWT_SECRET)
.compact();
}
// UserDetail으로 Autentication(인증객체) 만들기
public Authentication getAuthentication(MemberDetail userDetails) {
return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
}
// 토큰을 해독하여 사용자 정보 찾기 및 UserDetail 생성
public MemberDetail getMemberDetail(String token){
return new MemberDetail(repo.findById(Long.parseLong(this.getUserPk(token))));
}
// 토큰에서 회원 정보 PK 추출
public String getUserPk(String token) {
return Jwts.parser().setSigningKey(JWT_SECRET).parseClaimsJws(token).getBody().get("id").toString();
}
// 토큰의 유효성 + 만료일자 확인
public boolean validateToken(String jwtToken) {
try {
Jws<Claims> claims = Jwts.parser().setSigningKey(JWT_SECRET).parseClaimsJws(jwtToken);
return !claims.getBody().getExpiration().before(new Date());
} catch (Exception e) {
return false;
}
}
}
Step 4-1. 로그인 Filter 만들기
Jwt를 사용하기 위해 spring으로 오는 모든 요청에 대해 검사를 해야할 필요가 있다. 이때 사용하는것이 fiilter이다.
먼저 로그인에 대해 필터를 만들도록 하자
@Component
@RequiredArgsConstructor
public class LoginFilter extends UsernamePasswordAuthenticationFilter {
private final MemberRepo repo;
private final JwtTokenProvider provider;
// 로그인 시도시 해당 attemptAuthentication 실행
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
String id = request.getParameter("id");
String pw = request.getParameter("pw");
Member member = repo.findByLoginIdAndLoginPw(id,pw); //Id와 Pw으로 사용자 정보 조회
if(member != null) {
// 사용자 정보가 조회되면 해당 정보로 jwt 생성 및 쿠키 저장 및 인증 객체 재공
String token = provider.generateToken(member);
Cookie tokenCookie = CookieService.createCookie("myJwtToken",token);
response.addCookie(tokenCookie);
return provider.getAuthentication(new MemberDetail(member));
}
return super.attemptAuthentication(request, response);
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
response.sendRedirect("로그인 성공시 이동할 페이지 uri");
}
}
Step 4-2. Jwt 인증 Filter 만들기
@Component
public class JwtAuthFilter extends OncePerRequestFilter {
private JwtTokenProvider provider;
@Autowired
public JwtAuthFilter(JwtTokenProvider provider){
this.provider=provider;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
// jwt 쿠키 조회
Cookie cookie = CookieService.getCookie(request, "myJwtToken");
// Jwt cookie가 있는지 확인
if (cookie != null) {
String jwtToken = cookie.getValue();
// 해당 토큰의 유효성 검사
if (provider.validateToken(jwtToken)) {
// 토큰을 해독하여 유저 정보 조회
MemberDetail detail = provider.getMemberDetail(jwtToken);
if(detail.getMember() != null) {
Authentication authentication = provider.getAuthentication(detail);
// SecurityContext 에 Authentication 객체를 저장
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
}
chain.doFilter(request, response);
}
}
Step 5. Spring Security Configuration 만들기
여기까지 진행되었으면 마지막으로 spring security에서 해당 filter을 사용하도록 조작만 하면 된다.
@Configuration
@RequiredArgsConstructor
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{
private final LoginFilter loginFilter;
private final JwtAuthFilter jwtfilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
loginFilter.setFilterProcessesUrl("/login"); // 로그인 process url 경로 설정
http.csrf().disable();
// spring security는 인증정보를 sessionstorage에 저장한다.
// jwt를 사용할 경우 client가 인증정보를 저장하고 있으므로 sessionstorage를 비활성화 한다.
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// 필터 추가
http.addFilterBefore(jwtfilter, UsernamePasswordAuthenticationFilter.class);
http.addFilterBefore(loginfilter, UsernamePasswordAuthenticationFilter.class);
// 나머지 설정(sample)
// 로그인 process는 인증없이도 요청할 수 있도록 허용해야한다.
http
.authorizeRequests()
.antMatchers("/loginpage").permitAll()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.logout()
.logoutUrl("/logout")
.deleteCookies("myJwtToken").permitAll()
}
}
요즘은 jwt 토큰을 header에 저장하고 있다던데 따로 공부를 해야할것 같다.
'Spring' 카테고리의 다른 글
[Spring] Spring Security + OAuth2 (2) - 코드 구현하기 (0) | 2022.06.08 |
---|---|
[Spring] Spring Security + OAuth2 (1) - 프로젝트 설정하기 (0) | 2022.06.08 |
[Spring 기초] Scope 사용하기 (0) | 2022.06.07 |
[Spring 기초] 트랜잭션 (0) | 2022.06.06 |
[Spring 기초] Bean 생명주기 콜백 (0) | 2022.06.06 |