# Spring security6 + OAuth2 + JWT
###### tags: `spring`
Sping Security 架構圖

於src/main/repository/certs下鍵入cmd,建立RSA私鑰、公鑰(產出後keypair.pem就沒用,可刪除)
```bash=
# create rsa key pair
openssl genrsa -out keypair.pem 2048
# extract public key
openssl rsa -in keypair.pem -pubout -out public.pem
# create private key in PKCS#8 format
openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in keypair.pem -out private.pem
```
設定檔(application.yml)增加設定
```yaml=
rsa:
private-key: classpath:certs/private.pem
public-key: classpath:certs/public.pem
```
建立屬性設定 RsaKeyProperties.java
```java=
@ConfigurationProperties(prefix = "rsa")
public record RsaKeyProperties(RSAPublicKey publicKey, RSAPrivateKey privateKey) {
}
```
於啟動位置(Application.java)增加讀取屬性class
```java=
@EnableConfigurationProperties(RsaKeyProperties.class)
@SpringBootApplication
public class TryJpaApplication {
public static void main(String[] args) {
SpringApplication.run(TryJpaApplication.class, args);
}
}
```
新增Configuration檔(SecurityCofig)
```java=
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final RsaKeyProperties rsaKeys;
public SecurityConfig(RsaKeyProperties rsaKeys) {
this.rsaKeys = rsaKeys;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable())
.oauth2ResourceServer(OAuth2ResourceServerConfigurer :: jwt)
.sessionManagement(session ->session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterBefore(new AuthorizationCheckFilter(), BasicAuthenticationFilter.class)
.build();
}
@Bean
public InMemoryUserDetailsManager users() {
return new InMemoryUserDetailsManager();
}
@Bean
JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withPublicKey(rsaKeys.publicKey()).build();
}
@Bean
JwtEncoder jwtEncoder() {
JWK jwk = new RSAKey.Builder(rsaKeys.publicKey()).privateKey(rsaKeys.privateKey()).build();
JWKSource<SecurityContext> jwks = new ImmutableJWKSet<>(new JWKSet(jwk));
return new NimbusJwtEncoder(jwks);
}
}
```
建立登入攔截器 AuthorizationCheckFilter
```java=
public class AuthorizationCheckFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws ServletException, IOException {
//如果不是登入就攔截
String asd = req.getServletPath();
if(!req.getServletPath().contains("/login")){
String authorHeader = req.getHeader(AUTHORIZATION);
String bearer ="Bearer ";
//以jjwt驗證token,只要驗證成功就放行
//驗證失敗會拋exception,直接將錯誤訊息傳回
if(authorHeader!= null && authorHeader.startsWith(bearer)){
try{
String token = authorHeader.substring(bearer.length());
// Claims claims = Jwts.parser().setSigningKey("self")
// .parseClaimsJws(token).getBody();
// System.out.println("JWT payload:"+claims.toString());
chain.doFilter(req, res);
}catch(Exception e){
System.err.println("Error : "+e);
res.setStatus(FORBIDDEN.value());
Map<String, String> err = new HashMap<>();
err.put("jwt_err", e.getMessage());
res.setContentType(APPLICATION_JSON_VALUE);
new ObjectMapper().writeValue(res.getOutputStream(), err);
}
}else{
res.setStatus(UNAUTHORIZED.value());
}
}else{
chain.doFilter(req, res);
}
}
}
```
登入Controller
```java=
@PostMapping("/user/login")
public String token(@RequestBody login login) {
LOG.debug("Token requested for user: '{}'", login.getUsername());
String token = tokenService.generateToken(login.getUsername());
LOG.debug("Token granted: {}", token);
return token;
}
```
實作產出Token
```java=
@Service
public class TokenService {
private final JwtEncoder encoder;
public TokenService(JwtEncoder encoder) {
this.encoder = encoder;
}
public String generateToken(String username) {
Instant now = Instant.now();
JwtClaimsSet claims = JwtClaimsSet.builder()
.issuer("self")
.issuedAt(now)
.expiresAt(now.plus(20, ChronoUnit.MINUTES))
.subject(username)
.build();
return this.encoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();
}
}
```
Ref:
[Spring Security JWT](https://github.com/danvega/jwt)
[[Day 10] - Spring Boot 實作登入驗證(四)(JWT登入驗證)](https://ithelp.ithome.com.tw/articles/10272035)
[Spring Security 用SecurityFilterChain實作 Restful Api](https://www.bezkoder.com/spring-boot-login-example-mysql/)
[Spring Security 架構參考](https://www.bezkoder.com/spring-boot-jwt-mysql-spring-security-architecture/)