pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 追加 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<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>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test-autoconfigure</artifactId>
<scope>test</scope>
</dependency>
application.properties
secret=somerandomsecret
main
package jp.kd2.jwttest;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class JwttestApplication {
public static void main(String[] args) {
SpringApplication.run(JwttestApplication.class, args);
}
}
RequestModel
package jp.kd2.jwttest.model;
import java.io.Serializable;
public class JwtRequest implements Serializable {
private static final long serialVersionUID = 1L;
private String username;
private String password;
public JwtRequest() {
}
public JwtRequest(String username, String password) {
super();
this.username = username; this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
ResponseModel
package jp.kd2.jwttest.model;
import java.io.Serializable;
public class JwtResponse implements Serializable {
private static final long serialVersionUID = 1L;
private final String token;
public JwtResponse(String token) {
this.token = token;
}
public String getToken() {
return token;
}
}
RestController
package jp.kd2.jwttest.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloWorldController {
@GetMapping("/hello")
public String hello() {
return "{ \"greeting\" : \"Hello World Rest\" }";
}
}
JWTController
package jp.kd2.jwttest.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import jp.kd2.jwttest.service.JwtUserDetailsService;
import jp.kd2.jwttest.model.JwtRequest;
import jp.kd2.jwttest.model.JwtResponse;
import jp.kd2.jwttest.util.JwtTokenManager;
@RestController
@CrossOrigin
public class JwtController {
@Autowired
private JwtUserDetailsService userDetailsService;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtTokenManager tokenManager;
@PostMapping("/login")
public ResponseEntity<?> createToken(
@RequestBody JwtRequest request) throws Exception {
try {
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
request.getUsername(), request.getPassword()
)
);
} catch (DisabledException e) {
throw new Exception("USER_DISABLED", e);
} catch (BadCredentialsException e) {
throw new Exception("INVALID_CREDENTIALS", e);
}
final UserDetails userDetails = userDetailsService.loadUserByUsername(request.getUsername());
final String jwtToken = tokenManager.generateJwtToken(userDetails);
return ResponseEntity.ok(new JwtResponse(jwtToken));
}
}
SecurityConfig
package jp.kd2.jwttest.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import jp.kd2.jwttest.util.JwtAuthenticationEntryPoint;
import jp.kd2.jwttest.util.JwtFilter;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class JwtSecurityConfig {
@Autowired
private JwtAuthenticationEntryPoint authenticationEntryPoint;
@Autowired
private JwtFilter filter;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest().authenticated();
http.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedMethods("*");
}
};
}
}
Service
package jp.kd2.jwttest.service;
import java.util.ArrayList;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@Service
public class JwtUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
if ("jwtuser".equals(username)) {
return new User("jwtuser",
"$2a$10$Ik4eh/jP3VBumhbcCfonseroGTrL1brrSBirm3aOXWJB09N9734/i",
new ArrayList<>());
} else {
throw new UsernameNotFoundException("User was not found : " + username);
}
}
}
EntryPoint
package jp.kd2.jwttest.util;
import java.io.IOException;
import java.io.Serializable;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
@Component
public class JwtAuthenticationEntryPoint
implements AuthenticationEntryPoint, Serializable {
@Override
public void commence(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException)
throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
}
Filter
package jp.kd2.jwttest.util;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import io.jsonwebtoken.ExpiredJwtException;
import jp.kd2.jwttest.service.JwtUserDetailsService;
@Component
public class JwtFilter extends OncePerRequestFilter {
@Autowired
private JwtUserDetailsService userDetailsService;
@Autowired
private JwtTokenManager tokenManager;
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
String tokenHeader = request.getHeader("Authorization");
String username = null;
String token = null;
if (tokenHeader != null && tokenHeader.startsWith("Bearer ")) {
token = tokenHeader.substring(7);
try {
username = tokenManager.getUsernameFromToken(token);
} catch (IllegalArgumentException e) {
System.out.println("Unable to get JWT Token");
} catch (ExpiredJwtException e) {
System.out.println("JWT Token has expired");
}
} else {
System.out.println("Bearer String was not found in token");
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (tokenManager.validateJwtToken(token, userDetails)) {
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authenticationToken.setDetails(
new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}
filterChain.doFilter(request, response);
}
}
TokenManager
package jp.kd2.jwttest.util;
import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
@Component
public class JwtTokenManager implements Serializable {
private static final long serialVersionUID = 1L;
public static final long TOKEN_VALIDITY = 600; // 600sec = 10mins
@Value("${secret}")
private String jwtSecret;
public String generateJwtToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return Jwts.builder().setClaims(claims).setSubject(userDetails.getUsername())
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + TOKEN_VALIDITY * 1000))
.signWith(SignatureAlgorithm.HS512, jwtSecret).compact();
}
public Boolean validateJwtToken(String token, UserDetails userDetails) {
String username = getUsernameFromToken(token);
Claims claims = Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody();
Boolean isTokenExpired = claims.getExpiration().before(new Date());
return (username.equals(userDetails.getUsername()) && !isTokenExpired);
}
public String getUsernameFromToken(String token) {
final Claims claims = Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody();
return claims.getSubject();
}
}
JUnitでテストするクラス。
package jp.kd2.jwttest;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
/**
* テストクラス
*/
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class JwtAuthenticationTest {
@Autowired
private MockMvc mockMvc;
@Test
public void testJwtAuthentication() throws Exception {
// 正しいユーザ/パスワード : 200が返る。
this.mockMvc.perform(post("/login")
.content("{ \"username\":\"jwtuser\", \"password\":\"password\" }")
.contentType("application/json"))
.andDo(print()).andExpect(status().isOk());
// 間違ったパスワード : 401が返る。
this.mockMvc.perform(post("/login")
.content("{ \"username\":\"jwtuser\", \"password\":\"wrongpassword\" }")
.contentType("application/json"))
.andDo(print()).andExpect(status().is4xxClientError());
}
}
パスワードを確認するクラス。
package jp.kd2.jwttest;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public class PasswordTest {
public static void main(String[] args) {
// 同じパスワードでも毎回違う値が返ることを確認する。
for (int i = 0; i < 100; i++) {
String password = "password";
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String encrypted = passwordEncoder.encode(password);
System.out.println(encrypted);
}
}
}