Skip to content

Commit 2ed0abc

Browse files
committed
feature: springdoc-openapi-ui with oauth2-autoconfigure.
- JWT Authentication for default api - OAuth 2.0 Bearer Tokens for oauth2 api - Swagger UI JWT Default tokens
1 parent d75928c commit 2ed0abc

37 files changed

+1373
-8
lines changed

README.md

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
# Spring Boot 2.7.18
1+
# Spring Boot 2 + Springdoc Swagger UI
22

33
## How to settings with Intellij IDEA
44
1. Settings → Editor → Code Style → Enable editorconfig support
55
2. Import Scheme → Checkstyle configuration → checkstyle.xml in project folder
66
3. Install CheckStyle Plugin → Add checkstyle.xml and activate
77

8-
## Why do you need this repository?
9-
You can no longer create a Spring Boot 2 project through [Spring Initializr](https://start.spring.io/). If you know how to use a docker, you can use the [Spring Initializr docker image](https://hub.docker.com/r/kdevkr/spring-initializr) I shared.
8+
## Dependencies
109

10+
- [spring-security-oauth2-autoconfigure](https://github.com/spring-attic/spring-security-oauth2-boot/tree/main/spring-security-oauth2-autoconfigure)
11+
- [springdoc-openapi v1.7.0](https://springdoc.org/v1/)

build.gradle

+4
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,17 @@ dependencies {
3131
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
3232
implementation 'org.springframework.boot:spring-boot-starter-quartz'
3333
implementation 'org.springframework.boot:spring-boot-starter-security'
34+
implementation 'org.springframework.security.oauth.boot:spring-security-oauth2-autoconfigure:2.6.8'
3435
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
3536
implementation 'org.springframework.boot:spring-boot-starter-validation'
3637
implementation 'org.springframework.boot:spring-boot-starter-web'
3738
implementation 'org.springframework.cloud:spring-cloud-starter-sleuth'
3839
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
3940
implementation 'org.springdoc:springdoc-openapi-ui:1.7.0'
4041
implementation 'org.springdoc:springdoc-openapi-security:1.7.0'
42+
implementation 'io.jsonwebtoken:jjwt-api:0.12.5'
43+
implementation 'io.jsonwebtoken:jjwt-impl:0.12.5'
44+
implementation 'io.jsonwebtoken:jjwt-jackson:0.12.5'
4145
testImplementation 'org.testcontainers:junit-jupiter'
4246
compileOnly 'org.projectlombok:lombok'
4347
runtimeOnly 'com.h2database:h2'

src/main/java/com/example/demo/Application.java

+8
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
package com.example.demo;
22

3+
import java.util.TimeZone;
4+
import lombok.extern.slf4j.Slf4j;
35
import org.springframework.boot.SpringApplication;
46
import org.springframework.boot.autoconfigure.SpringBootApplication;
7+
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
8+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
59

10+
@Slf4j
11+
@ConfigurationPropertiesScan
12+
@EnableConfigurationProperties
613
@SpringBootApplication
714
public class Application {
815

916
public static void main(String[] args) {
17+
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
1018
SpringApplication.run(Application.class, args);
1119
}
1220

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package com.example.demo.auth;
2+
3+
import com.example.demo.user.UserClient;
4+
import com.example.demo.user.UserService;
5+
import java.io.Serializable;
6+
import lombok.AllArgsConstructor;
7+
import org.springframework.security.access.PermissionEvaluator;
8+
import org.springframework.security.core.Authentication;
9+
import org.springframework.security.core.context.SecurityContextHolder;
10+
import org.springframework.security.oauth2.provider.ClientDetails;
11+
import org.springframework.stereotype.Component;
12+
13+
@AllArgsConstructor
14+
@Component("auth")
15+
public class AuthPermissionEvaluator implements PermissionEvaluator {
16+
private final UserService userService;
17+
18+
public boolean isSystem() {
19+
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
20+
ClientDetails clientDetails = userService.loadClientByClientId(authentication.getName());
21+
if (clientDetails instanceof UserClient) {
22+
UserClient userClient = (UserClient) clientDetails;
23+
return "system".equals(userClient.getUser().getId()) && userClient.getScope().contains("all");
24+
}
25+
return false;
26+
}
27+
28+
@Override
29+
public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
30+
return true;
31+
}
32+
33+
@Override
34+
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
35+
return true;
36+
}
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.example.demo.auth;
2+
3+
import org.springframework.context.annotation.Bean;
4+
import org.springframework.context.annotation.Configuration;
5+
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
6+
import org.springframework.security.crypto.password.PasswordEncoder;
7+
8+
@Configuration
9+
public class PasswordConfiguration {
10+
@Bean
11+
public PasswordEncoder passwordEncoder() {
12+
return new BCryptPasswordEncoder(12);
13+
}
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.example.demo.auth;
2+
3+
import java.util.HashMap;
4+
import java.util.List;
5+
import java.util.Map;
6+
import org.springframework.context.annotation.Bean;
7+
import org.springframework.context.annotation.Configuration;
8+
import org.springframework.core.annotation.Order;
9+
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
10+
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
11+
import org.springframework.security.access.hierarchicalroles.RoleHierarchyUtils;
12+
import org.springframework.security.access.vote.RoleHierarchyVoter;
13+
14+
@Order(0)
15+
@Configuration
16+
public class RoleHierarchyConfiguration {
17+
@Bean
18+
public RoleHierarchy roleHierarchy() {
19+
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
20+
Map<String, List<String>> roleHierarchyMap = new HashMap<>();
21+
roleHierarchyMap.put("ROLE_SYSTEM", List.of("ROLE_ADMIN"));
22+
roleHierarchyMap.put("ROLE_ADMIN", List.of("ROLE_USER"));
23+
String hierarchy = RoleHierarchyUtils.roleHierarchyFromMap(roleHierarchyMap);
24+
roleHierarchy.setHierarchy(hierarchy);
25+
return roleHierarchy;
26+
}
27+
28+
@Bean
29+
public RoleHierarchyVoter roleHierarchyVoter() {
30+
RoleHierarchy roleHierarchy = roleHierarchy();
31+
return new RoleHierarchyVoter(roleHierarchy);
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package com.example.demo.auth;
2+
3+
import com.example.demo.auth.jwt.JwtAuthenticationFilter;
4+
import com.example.demo.auth.jwt.JwtFormLoginSuccessHandler;
5+
import com.example.demo.user.UserService;
6+
import lombok.AllArgsConstructor;
7+
import org.springframework.beans.factory.annotation.Autowired;
8+
import org.springframework.context.annotation.Bean;
9+
import org.springframework.context.annotation.Configuration;
10+
import org.springframework.http.HttpHeaders;
11+
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
12+
import org.springframework.security.authentication.AuthenticationManager;
13+
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
14+
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
15+
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
16+
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
17+
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
18+
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
19+
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
20+
import org.springframework.security.config.http.SessionCreationPolicy;
21+
import org.springframework.security.crypto.password.PasswordEncoder;
22+
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
23+
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
24+
import org.springframework.security.web.context.RequestAttributeSecurityContextRepository;
25+
26+
@AllArgsConstructor
27+
@EnableMethodSecurity
28+
@EnableWebSecurity
29+
@Configuration
30+
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
31+
32+
private final UserService userService;
33+
private final PasswordEncoder passwordEncoder;
34+
private final RoleHierarchy roleHierarchy;
35+
36+
@Bean
37+
@Override
38+
public AuthenticationManager authenticationManagerBean() throws Exception {
39+
return super.authenticationManagerBean();
40+
}
41+
42+
@Autowired
43+
@Override
44+
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
45+
auth.userDetailsService(userService).passwordEncoder(passwordEncoder);
46+
}
47+
48+
@Autowired
49+
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
50+
auth.userDetailsService(userService).passwordEncoder(passwordEncoder);
51+
}
52+
53+
@Override
54+
protected void configure(HttpSecurity http) throws Exception {
55+
http.csrf(AbstractHttpConfigurer::disable);
56+
http.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin));
57+
http.httpBasic(AbstractHttpConfigurer::disable);
58+
59+
http.formLogin()
60+
.successHandler(new JwtFormLoginSuccessHandler())
61+
.and()
62+
.logout().logoutUrl("/logout")
63+
.permitAll().deleteCookies(HttpHeaders.AUTHORIZATION);
64+
65+
http.authorizeRequests()
66+
.antMatchers("/h2-console", "/h2-console/**").permitAll()
67+
.antMatchers("/swagger-ui.html", "/swagger-ui/**", "/v3/api-docs/**").permitAll()
68+
.antMatchers("/login", "/error").permitAll()
69+
.anyRequest().authenticated()
70+
.expressionHandler(webSecurityExpressionHandler())
71+
.and()
72+
.addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
73+
74+
http.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
75+
http.securityContext(sc -> sc.securityContextRepository(new RequestAttributeSecurityContextRepository()));
76+
}
77+
78+
@Bean
79+
public DefaultWebSecurityExpressionHandler webSecurityExpressionHandler() {
80+
DefaultWebSecurityExpressionHandler webSecurityExpressionHandler = new DefaultWebSecurityExpressionHandler();
81+
webSecurityExpressionHandler.setRoleHierarchy(roleHierarchy);
82+
return webSecurityExpressionHandler;
83+
}
84+
85+
86+
87+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package com.example.demo.auth.jwt;
2+
3+
import io.jsonwebtoken.*;
4+
import io.jsonwebtoken.impl.DefaultJws;
5+
import java.io.IOException;
6+
import java.util.ArrayList;
7+
import java.util.List;
8+
import javax.servlet.FilterChain;
9+
import javax.servlet.ServletException;
10+
import javax.servlet.http.Cookie;
11+
import javax.servlet.http.HttpServletRequest;
12+
import javax.servlet.http.HttpServletResponse;
13+
import lombok.extern.slf4j.Slf4j;
14+
import org.springframework.http.HttpHeaders;
15+
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
16+
import org.springframework.security.core.GrantedAuthority;
17+
import org.springframework.security.core.authority.SimpleGrantedAuthority;
18+
import org.springframework.security.core.context.SecurityContextHolder;
19+
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
20+
import org.springframework.util.StringUtils;
21+
import org.springframework.web.filter.OncePerRequestFilter;
22+
import org.springframework.web.util.WebUtils;
23+
24+
@Slf4j
25+
public class JwtAuthenticationFilter extends OncePerRequestFilter {
26+
public static final String AUTHORIZATION_HEADER_PREFIX = "Bearer ";
27+
28+
@Override
29+
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
30+
if (request.getRequestURI().startsWith("/oauth")) { // NOTE: Exclude oauth endpoint url.
31+
filterChain.doFilter(request, response);
32+
return;
33+
}
34+
35+
String token = null;
36+
37+
String authorization = request.getHeader(HttpHeaders.AUTHORIZATION);
38+
if (authorization != null && authorization.startsWith(AUTHORIZATION_HEADER_PREFIX)) {
39+
token = authorization.substring(AUTHORIZATION_HEADER_PREFIX.length());
40+
}
41+
42+
Cookie cookie = WebUtils.getCookie(request, HttpHeaders.AUTHORIZATION);
43+
if (token == null && cookie != null) {
44+
token = cookie.getValue();
45+
}
46+
47+
if (StringUtils.hasText(token)) {
48+
DefaultJws<Claims> jws = JwtUtil.parse(token);
49+
Claims claims = jws.getPayload();
50+
51+
List<GrantedAuthority> authorities = new ArrayList<>();
52+
53+
if ("system".equals(claims.getSubject())) {
54+
authorities.add(new SimpleGrantedAuthority("ROLE_SYSTEM"));
55+
} else {
56+
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
57+
}
58+
59+
UsernamePasswordAuthenticationToken authenticationToken =
60+
new UsernamePasswordAuthenticationToken(claims.getSubject(), null, authorities);
61+
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
62+
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
63+
}
64+
65+
filterChain.doFilter(request, response);
66+
}
67+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.example.demo.auth.jwt;
2+
3+
import com.example.demo.user.User;
4+
import java.io.IOException;
5+
import java.time.Duration;
6+
import java.time.Instant;
7+
import java.time.temporal.ChronoUnit;
8+
import java.util.Date;
9+
import javax.servlet.ServletException;
10+
import javax.servlet.http.HttpServletRequest;
11+
import javax.servlet.http.HttpServletResponse;
12+
import lombok.extern.slf4j.Slf4j;
13+
import org.springframework.http.HttpHeaders;
14+
import org.springframework.security.core.Authentication;
15+
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
16+
import org.springframework.web.util.CookieGenerator;
17+
18+
@Slf4j
19+
public class JwtFormLoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
20+
21+
@Override
22+
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
23+
Instant now = Instant.now();
24+
User principal = (User) authentication.getPrincipal();
25+
26+
String jwt = JwtUtil.builder()
27+
.subject(principal.getId())
28+
.claim("name", authentication.getName())
29+
.issuedAt(Date.from(now))
30+
.expiration(Date.from(now.plus(30, ChronoUnit.DAYS)))
31+
.compact();
32+
33+
CookieGenerator cookieGenerator = new CookieGenerator();
34+
cookieGenerator.setCookieName(HttpHeaders.AUTHORIZATION);
35+
cookieGenerator.setCookiePath("/");
36+
cookieGenerator.setCookieSecure(true);
37+
cookieGenerator.setCookieHttpOnly(true);
38+
cookieGenerator.setCookieMaxAge((int) Duration.ofHours(1).toSeconds()); // seconds
39+
cookieGenerator.addCookie(response, jwt);
40+
41+
super.onAuthenticationSuccess(request, response, authentication);
42+
}
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.example.demo.auth.jwt;
2+
3+
import io.jsonwebtoken.Claims;
4+
import io.jsonwebtoken.JwtBuilder;
5+
import io.jsonwebtoken.Jwts;
6+
import io.jsonwebtoken.impl.DefaultJws;
7+
import io.jsonwebtoken.security.Keys;
8+
import java.util.Base64;
9+
import javax.crypto.SecretKey;
10+
import lombok.experimental.UtilityClass;
11+
12+
@UtilityClass
13+
public class JwtUtil {
14+
public static SecretKey secretKey() {
15+
byte[] bytes = Base64.getDecoder().decode("NwDDyzuXUkReFuwPyMjb3r6aDQAoj63Yu2FjiJjS3wFbvjK7");
16+
return Keys.hmacShaKeyFor(bytes);
17+
}
18+
19+
public static JwtBuilder builder() {
20+
return Jwts.builder().signWith(JwtUtil.secretKey());
21+
}
22+
23+
@SuppressWarnings("unchecked")
24+
public static DefaultJws<Claims> parse(String token) {
25+
return (DefaultJws<Claims>) Jwts.parser()
26+
.verifyWith(JwtUtil.secretKey()).build()
27+
.parse(token);
28+
}
29+
}

0 commit comments

Comments
 (0)