Skip to content

Commit bcc7f6f

Browse files
author
jianggang
authored
feat: add jwt authentication (#22)
1 parent 66a4ea0 commit bcc7f6f

7 files changed

Lines changed: 150 additions & 9 deletions

File tree

pom.xml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -220,10 +220,6 @@
220220
<version>${version.powermock}</version>
221221
<scope>test</scope>
222222
</dependency>
223-
<dependency>
224-
<groupId>org.springframework.session</groupId>
225-
<artifactId>spring-session-jdbc</artifactId>
226-
</dependency>
227223
<dependency>
228224
<groupId>org.springframework.boot</groupId>
229225
<artifactId>spring-boot-starter-test</artifactId>
@@ -235,6 +231,11 @@
235231
</exclusion>
236232
</exclusions>
237233
</dependency>
234+
<dependency>
235+
<groupId>com.auth0</groupId>
236+
<artifactId>java-jwt</artifactId>
237+
<version>4.0.0</version>
238+
</dependency>
238239
</dependencies>
239240

240241
<build>
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package com.featureprobe.api.auth;
2+
3+
import com.auth0.jwt.JWT;
4+
import com.auth0.jwt.JWTVerifier;
5+
import com.auth0.jwt.algorithms.Algorithm;
6+
import com.auth0.jwt.interfaces.DecodedJWT;
7+
import com.featureprobe.api.base.exception.ForbiddenException;
8+
import com.featureprobe.api.entity.Member;
9+
import com.featureprobe.api.mapper.JsonMapper;
10+
import com.featureprobe.api.repository.MemberRepository;
11+
import lombok.extern.slf4j.Slf4j;
12+
import org.apache.commons.lang3.StringUtils;
13+
import org.springframework.security.authentication.AuthenticationManager;
14+
import org.springframework.security.core.authority.SimpleGrantedAuthority;
15+
import org.springframework.security.core.context.SecurityContextHolder;
16+
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
17+
import org.springframework.util.Base64Utils;
18+
import javax.servlet.FilterChain;
19+
import javax.servlet.ServletException;
20+
import javax.servlet.http.HttpServletRequest;
21+
import javax.servlet.http.HttpServletResponse;
22+
import java.io.IOException;
23+
import java.nio.charset.StandardCharsets;
24+
import java.util.Arrays;
25+
import java.util.Date;
26+
import java.util.Map;
27+
28+
@Slf4j
29+
public class JWTAuthenticationFilter extends BasicAuthenticationFilter {
30+
31+
private static final String TOKEN_KEY = "token";
32+
33+
MemberRepository memberRepository;
34+
35+
public JWTAuthenticationFilter(AuthenticationManager authenticationManager, MemberRepository memberRepository) {
36+
super(authenticationManager);
37+
this.memberRepository = memberRepository;
38+
}
39+
40+
@Override
41+
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
42+
throws IOException, ServletException {
43+
final String authHeader = request.getHeader(JWTUtils.AUTH_HEADER_KEY);
44+
if (StringUtils.isBlank(authHeader) || !authHeader.startsWith(JWTUtils.TOKEN_PREFIX)) {
45+
chain.doFilter(request, response);
46+
return;
47+
}
48+
try {
49+
final String token = authHeader.substring(7);
50+
String payload = JWT.decode(token).getPayload();
51+
byte[] decode = Base64Utils.decode(payload.getBytes(StandardCharsets.UTF_8));
52+
String user = new String(decode);
53+
Long userId = Long.parseLong(String.valueOf(JsonMapper.toObject(user, Map.class).get("userId")));
54+
Member member = memberRepository.findById(userId).orElseThrow(() -> new ForbiddenException());
55+
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(member.getPassword())).build();
56+
DecodedJWT decodedJWT = verifier.verify(token);
57+
Date expiresAt = decodedJWT.getExpiresAt();
58+
if (new Date().after(expiresAt)) {
59+
log.error("Token has expired");
60+
throw new ForbiddenException();
61+
}
62+
UserPasswordAuthenticationToken userPasswordAuthenticationToken =
63+
new UserPasswordAuthenticationToken(member,
64+
Arrays.asList(new SimpleGrantedAuthority(member.getRole().name())));
65+
SecurityContextHolder.getContext().setAuthentication(userPasswordAuthenticationToken);
66+
if ((expiresAt.getTime() - new Date().getTime()) < 600) {
67+
response.setHeader(TOKEN_KEY, JWTUtils.getToken(member));
68+
} else {
69+
response.setHeader(TOKEN_KEY, token);
70+
}
71+
}catch (Exception e) {
72+
log.error("Token is forbidden", e);
73+
throw new ForbiddenException();
74+
}
75+
chain.doFilter(request, response);
76+
}
77+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.featureprobe.api.auth;
2+
3+
import com.auth0.jwt.JWT;
4+
import com.auth0.jwt.algorithms.Algorithm;
5+
import com.featureprobe.api.entity.Member;
6+
7+
import java.time.LocalDateTime;
8+
import java.time.ZoneId;
9+
10+
public class JWTUtils {
11+
12+
public static final String AUTH_HEADER_KEY = "Authorization";
13+
public static final String TOKEN_PREFIX = "Bearer ";
14+
private static final String ISS_KEY = "iss";
15+
private static final String ISS_VALUE = "https://featureprobe.com";
16+
private static final String ACCOUNT_KEY = "account";
17+
private static final String USER_ID_KEY = "userId";
18+
private static final String ROLE_KEY = "role";
19+
20+
public static String getToken(Member member) {
21+
LocalDateTime now = LocalDateTime.now();
22+
return JWT.create()
23+
.withExpiresAt(LocalDateTime.now().plusMinutes(30L).atZone(ZoneId.systemDefault()).toInstant())
24+
.withClaim(ISS_KEY, ISS_VALUE)
25+
.withClaim(ACCOUNT_KEY, member.getAccount())
26+
.withClaim(USER_ID_KEY, member.getId())
27+
.withClaim(ROLE_KEY, member.getRole().name())
28+
.sign(Algorithm.HMAC256(member.getPassword()));
29+
}
30+
31+
}

src/main/java/com/featureprobe/api/auth/LoginSuccessHandler.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
package com.featureprobe.api.auth;
22

3-
import com.featureprobe.api.dto.MemberResponse;
3+
import com.featureprobe.api.dto.CertificationUserResponse;
44
import com.featureprobe.api.mapper.JsonMapper;
5+
import lombok.AllArgsConstructor;
56
import org.springframework.http.HttpStatus;
67
import org.springframework.http.MediaType;
78
import org.springframework.security.core.Authentication;
@@ -15,6 +16,7 @@
1516
import java.nio.charset.StandardCharsets;
1617

1718
@Component
19+
@AllArgsConstructor
1820
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
1921

2022
@Override
@@ -25,7 +27,9 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo
2527
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
2628
UserPasswordAuthenticationToken token =
2729
(UserPasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
28-
response.getWriter().write(JsonMapper.toJSONString(new MemberResponse(token.getAccount(), token.getRole())));
30+
String jwt = JWTUtils.getToken(token.getPrincipal());
31+
response.getWriter()
32+
.write(JsonMapper.toJSONString(new CertificationUserResponse(token.getAccount(), token.getRole(),jwt)));
2933
}
3034

3135
}

src/main/java/com/featureprobe/api/auth/SecurityConfig.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.featureprobe.api.dto.BaseResponse;
44
import com.featureprobe.api.mapper.JsonMapper;
5+
import com.featureprobe.api.repository.MemberRepository;
56
import lombok.AllArgsConstructor;
67
import lombok.extern.slf4j.Slf4j;
78
import org.springframework.context.annotation.Bean;
@@ -12,9 +13,9 @@
1213
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
1314
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
1415
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
16+
import org.springframework.security.config.http.SessionCreationPolicy;
1517
import org.springframework.security.web.AuthenticationEntryPoint;
1618
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
17-
1819
import java.nio.charset.StandardCharsets;
1920

2021
@Slf4j
@@ -29,6 +30,8 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
2930

3031
private CustomLogoutSuccessHandler customLogoutSuccessHandler;
3132

33+
private MemberRepository memberRepository;
34+
3235
UserPasswordAuthenticationProcessingFilter userPasswordAuthenticationProcessingFilter(
3336
AuthenticationManager authenticationManager) {
3437
UserPasswordAuthenticationProcessingFilter userPasswordAuthenticationProcessingFilter =
@@ -39,6 +42,12 @@ UserPasswordAuthenticationProcessingFilter userPasswordAuthenticationProcessingF
3942
return userPasswordAuthenticationProcessingFilter;
4043
}
4144

45+
JWTAuthenticationFilter jwtAuthenticationFilter(AuthenticationManager authenticationManager,
46+
MemberRepository memberRepository) {
47+
JWTAuthenticationFilter jwtAuthenticationFilter =
48+
new JWTAuthenticationFilter(authenticationManager, memberRepository);
49+
return jwtAuthenticationFilter;
50+
}
4251

4352
@Bean
4453
AuthenticationEntryPoint authenticationEntryPoint() {
@@ -56,6 +65,7 @@ AuthenticationEntryPoint authenticationEntryPoint() {
5665

5766
@Override
5867
protected void configure(HttpSecurity http) throws Exception {
68+
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
5969
http.headers().frameOptions().disable();
6070
http.csrf().disable();
6171
http
@@ -75,8 +85,9 @@ protected void configure(HttpSecurity http) throws Exception {
7585
.accessDeniedHandler(((httpServletRequest, httpServletResponse, e) ->
7686
httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value())))
7787
.authenticationEntryPoint(authenticationEntryPoint());
78-
http.addFilterBefore(userPasswordAuthenticationProcessingFilter(authenticationManager()),
79-
UsernamePasswordAuthenticationFilter.class);
88+
http.addFilter(jwtAuthenticationFilter(authenticationManager(), memberRepository))
89+
.addFilterBefore(userPasswordAuthenticationProcessingFilter(authenticationManager()),
90+
UsernamePasswordAuthenticationFilter.class);
8091
}
8192

8293
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.featureprobe.api.dto;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Data;
5+
6+
@Data
7+
@AllArgsConstructor
8+
public class CertificationUserResponse {
9+
10+
private String account;
11+
12+
private String role;
13+
14+
private String token;
15+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
drop table SPRING_SESSION;
2+
drop table SPRING_SESSION_ATTRIBUTES;

0 commit comments

Comments
 (0)