# Spring security6 + OAuth2 + JWT ###### tags: `spring` Sping Security 架構圖 ![Sping Security 架構圖](https://www.bezkoder.com/wp-content/uploads/2020/05/spring-boot-jwt-mysql-spring-security-architecture.png) 於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/)