-
spring boot jwtIT/spring 2020. 12. 25. 20:15
spring boot jwt
dependencies {
compile group: 'io.jsonwebtoken', name: 'jjwt', version: '0.7.0'
compile 'io.jsonwebtoken:jjwt-api:0.10.7'
runtime 'io.jsonwebtoken:jjwt-impl:0.10.7'
runtime 'io.jsonwebtoken:jjwt-jackson:0.10.7'
}
application.yaml
project:
jwt:
secretKey: tototototototoknesesesesecrtkeyekekrkrk
tokenPrefix: "Bearer "
tokenExpirationAfterDays: 10@ToString
@Getter
@Setter
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
@Component
@Configuration
@EnableConfigurationProperties
@ConfigurationProperties(prefix = "project.jwt")
public class JwtConfig {
private String secretKey;
private String tokenPrefix;
private Long tokenExpirationAfterDays;
public JwtConfig() {
}
}@Configuration
public class JwtSecretKey {
private final JwtConfig jwtConfig;
@Autowired
public JwtSecretKey(JwtConfig jwtConfig) {
this.jwtConfig = jwtConfig;
}
}
public class JwtTokenVerifier extends OncePerRequestFilter {
private final JwtConfig jwtConfig;
private final CacheManager cacheManager;
private final TokenService tokenService;
public JwtTokenVerifier(JwtConfig jwtConfig, CacheManager cacheManager, TokenService tokenService) {
this.jwtConfig = jwtConfig;
this.cacheManager = cacheManager;
this.tokenService = tokenService;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String authorizationHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
if (Strings.isNullOrEmpty(authorizationHeader) || !authorizationHeader.startsWith(jwtConfig.getTokenPrefix())) {
filterChain.doFilter(request, response);
return;
}
try {
Jws<Claims> claimsJws = tokenService.parserJwt(authorizationHeader);
Claims body = claimsJws.getBody();
String username = body.getSubject();
UserAuthToken userAuthToken = tokenService.getToken(UserAuthToken.builder().name(username).build());
if (!authorizationHeader.equals(userAuthToken.getToken())){
throw new JwtException("cache not matching token");
}
List<Map<String, String>> authorities = (List<Map<String, String>>) body.get("authorities");
Set<SimpleGrantedAuthority> simpleGrantedAuthorities = authorities.stream()
.map(m -> new SimpleGrantedAuthority(m.get("authority")))
.collect(Collectors.toSet());
Authentication authentication = new UsernamePasswordAuthenticationToken(
username,
null,
simpleGrantedAuthorities
);
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (JwtException e) {
throw new IllegalStateException(String.format("Token %s cannot be trusted", authorizationHeader));
}
filterChain.doFilter(request, response);
}
}
public class JwtUsernameAndPasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
private final JwtConfig jwtConfig;
private final UserRepository userRepository;
public SecretKey secretKey;
public static final String JWT_TOKEN_PROCESSES_URL = WebSecurityConfigurerAdapter.SECURITY_PATH + "/user-sign-in";
private final TokenService tokenService;
private final CacheManager cacheManager;
public JwtUsernameAndPasswordAuthenticationFilter(AuthenticationManager authenticationManager,
JwtConfig jwtConfig,
CacheManager cacheManager,
TokenService tokenService,
UserRepository userRepository) {
this.userRepository = userRepository;
this.cacheManager = cacheManager;
this.tokenService = tokenService;
this.authenticationManager = authenticationManager;
this.jwtConfig = jwtConfig;
setFilterProcessesUrl(JWT_TOKEN_PROCESSES_URL);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
try {
UsernameAndPasswordAuthenticationRequest authenticationRequest = new ObjectMapper()
.readValue(request.getInputStream(), UsernameAndPasswordAuthenticationRequest.class);
String generatorPassword = userRepository.generatorPassword(authenticationRequest.getPassword());
authenticationRequest.setPassword(generatorPassword);
Authentication authentication = new UsernamePasswordAuthenticationToken(
authenticationRequest.getUsername(),
authenticationRequest.getPassword()
);
Authentication authenticate = authenticationManager.authenticate(authentication);
return authenticate;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication authResult) throws IOException, ServletException {
String token = tokenService.makeToken(authResult.getName(), authResult.getAuthorities());
String fullToken = jwtConfig.getTokenPrefix() + token;
response.addHeader(HttpHeaders.AUTHORIZATION, fullToken);
UserAuthToken userAuthToken = UserAuthToken.builder().name(authResult.getName()).refreshToken(tokenService.makeRefreshToken()).token(fullToken).build();
tokenService.putToken(userAuthToken);
ObjectMapper objectMapper = new ObjectMapper();
response.getWriter().write(objectMapper.writeValueAsString(userAuthToken));
}
}@ToString
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = false)
public class UsernameAndPasswordAuthenticationRequest {
private String username;
private String password;
}
@Slf4j
@Configuration
@EnableWebSecurity
public class WebSecurityConfigurerAdapter extends org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter {
@Value("${project.properties.username}")
String username;
@Value("${project.properties.password}")
String password;
public static final String SECURITY_PATH = "/securitys";
public static final String LOGOUT_URL = SECURITY_PATH+"/sign-out";
public static final String LOGIN_PAGE = "/#/login";
public static final String LOGIN_PROCESSING_URL = SECURITY_PATH+"/sign-in";
public static final String USERNAME_PARAMETER = "username";
public static final String USERPWD_PARAMETER = "password";
public static final String DEFAULT_SUCCESS_URL = "/";
private final PasswordEncoder passwordEncoder;
private final UserDetailsService applicationUserService;
private final UserRepository userRepository;
private final JwtConfig jwtConfig;
private final CacheManager cacheManager;
private final TokenService tokenService;
@Autowired
public WebSecurityConfigurerAdapter(PasswordEncoder passwordEncoder,
UserDetailsService applicationUserService,
JwtConfig jwtConfig,
CacheManager cacheManager,
TokenService tokenService,
UserRepository userRepository
) {
this.userRepository = userRepository;
this.cacheManager = cacheManager;
this.tokenService = tokenService;
this.passwordEncoder = passwordEncoder;
this.applicationUserService = applicationUserService;
this.jwtConfig = jwtConfig;
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(
"/assets/**",
"/*.css",
"/*.js",
"/*.map",
"/*.ico",
SecuritysController.URI_PREFIX+"/refresh"
);
}
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement()
.and()
.formLogin()
.loginPage(LOGIN_PAGE) //로그인 페이지
.loginProcessingUrl(LOGIN_PROCESSING_URL) //login-processing-url 로그인 페이지 form action에 입력할 주소 지정
.usernameParameter(USERNAME_PARAMETER)
.passwordParameter(USERPWD_PARAMETER)
.defaultSuccessUrl(DEFAULT_SUCCESS_URL) //성공시 이동될 페이지
.permitAll()
.and()
.logout()
.logoutUrl(LOGOUT_URL)
.invalidateHttpSession(true)
.logoutSuccessUrl(DEFAULT_SUCCESS_URL)
.and()
.addFilter(new JwtUsernameAndPasswordAuthenticationFilter(authenticationManager(), jwtConfig, cacheManager, tokenService, userRepository))
.addFilterAfter(new JwtTokenVerifier(jwtConfig, cacheManager, tokenService),JwtUsernameAndPasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/", "index","/favicon.ico", "*.css","/css/*", "/js/*").permitAll()
.antMatchers("/apis" ,"/apis/**").hasAnyRole(UserRole.USER.name(), UserRole.ADMIN.name())
.antMatchers("/swagger", "/swagger/**").hasRole(UserRole.SWAGGER.name())
.antMatchers("/docs", "/docs/**").hasRole(UserRole.DOCS.name())
.anyRequest()
.authenticated();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(daoAuthenticationProvider());
}
@Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setPasswordEncoder(passwordEncoder);
provider.setUserDetailsService(applicationUserService);
return provider;
}
}
@Service
public class TokenService {
public final static String TOKEN_KEY = "api-user-tokens";
@Autowired
public JwtConfig jwtConfig;
@Cacheable(cacheNames = TOKEN_KEY, key="#userAuthToken.name")
public UserAuthToken getToken(UserAuthToken userAuthToken){
return userAuthToken;
}
@CachePut(cacheNames = TOKEN_KEY, key="#userAuthToken.name")
public UserAuthToken putToken(UserAuthToken userAuthToken){
return userAuthToken;
}
@CacheEvict(cacheNames = TOKEN_KEY, key="#userAuthToken.name")
public void evictToken(UserAuthToken userAuthToken){
}
public Jws<Claims> parserJwt(String header) throws JwtException {
String token = header.replace(jwtConfig.getTokenPrefix(), "");
Jws<Claims> claimsJws = Jwts.parser()
.setSigningKey(this.jwtConfig.getSecretKey().getBytes(StandardCharsets.UTF_8))
.parseClaimsJws(token);
return claimsJws;
}
public String makeToken(String subject, Collection<? extends GrantedAuthority> bodyAuthorities) {
Date now = Date.from(LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant());
LocalDateTime localDateTime = LocalDateTime.now().plusDays(jwtConfig.getTokenExpirationAfterDays());
Date expirationDate = Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
String token = Jwts.builder()
.setSubject(subject)
.claim("authorities", bodyAuthorities)
.setIssuedAt(now)
.setExpiration(expirationDate)
.signWith(SignatureAlgorithm.HS512, this.jwtConfig.getSecretKey().getBytes(StandardCharsets.UTF_8))
.compact();
return token;
}
public String makeRefreshToken() {
String id = UUID.randomUUID().toString();
Base64.Encoder encoder = Base64.getEncoder();
String refreshtoken = new String(encoder.encode(id.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8);
return refreshtoken;
}
}refresh token
@Slf4j
@RestController
@RequestMapping(SecuritysController.URI_PREFIX)
public class SecuritysController {
public static final String URI_PREFIX = "/securitys";
@Autowired
CacheManager cacheManager;
@Autowired
TokenService tokenService;
@Autowired
JwtConfig jwtConfig;
@Autowired
UserRepository userRepository;
@GetMapping(value = {"", "/"})
public String securitys() {
return "securitys";
}
@PostMapping(value = "/refresh")
public UserAuthToken refresh(
HttpServletRequest request,
HttpServletResponse response,
@RequestHeader(value= HttpHeaders.AUTHORIZATION) String authorization_header,
@RequestBody UserRefreshToken refreshToken) {
String name = null;
try {
Jws<Claims> claimsJws = tokenService.parserJwt(authorization_header);
name = claimsJws.getBody().getSubject();
} catch (ClaimJwtException e) { // ExpiredJwtException
name = e.getClaims().getSubject();
}
// cache에 있는 값이랑 비교 token, refreshToken
UserAuthToken userAuthToken = tokenService.getToken(UserAuthToken.builder().name(name).build());
if (authorization_header.equals(userAuthToken.getToken()) && null != refreshToken && refreshToken.getRefreshToken().equals(userAuthToken.getRefreshToken())) {
Optional<User> userOption = userRepository.findByNo(Long.parseLong(name));
User user = userOption.orElseThrow(() -> new ErrorMsgException(new Error(MsgCode.E10003)));
String token = tokenService.makeToken(Long.toString(user.getNo()), user.getRoles());
String fullToken = jwtConfig.getTokenPrefix() + token;
response.addHeader(HttpHeaders.AUTHORIZATION, fullToken);
UserAuthToken newUserAuthToken = UserAuthToken.builder().name(Long.toString(user.getNo())).refreshToken(tokenService.makeRefreshToken()).token(fullToken).build();
tokenService.putToken(newUserAuthToken);
return newUserAuthToken;
} else {
throw new ErrorMsgException(new Error(MsgCode.E10003));
}
}
}visualkhh@gmail.com
감사합니다.
'IT > spring' 카테고리의 다른 글
spring boot 정리 (locale, messageSource, security, jpa, hibernate, Scheduler, config) (0) 2021.02.04 spring boot 에서 JOOQ 사용시, 구동 1분이상 느려짐 현상 (버그) (0) 2021.02.04 spring에서 FCM (firebase cloud messaging) push 보내기. (11) 2021.02.04 JWT Token을 spring security에서 손쉽게 검증하기 위한 방법 (0) 2021.01.26 spring boot 다중 커넥션 multiple dataSource + hikari (0) 2020.09.30