@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
private final LoginService loginService;
private final JwtService jwtService;
private final AdminRepository adminRepository;
private final ObjectMapper objectMapper;
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(cs -> cs.disable())
.sessionManagement(s->s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.formLogin(f->f.disable())
.httpBasic(h->h.disable());
http
.authorizeHttpRequests(auth->{
//// auth.requestMatchers("/lectures/**").hasAnyRole("MANAGER")
//// auth.requestMatchers("/instructors/**").hasAnyRole("MANAGER")
// auth.requestMatchers("/instructors/**").authenticated()
// .requestMatchers("/lectures/**").permitAll()
//// .requestMatchers("/instructors/**").hasAnyRole("MANAGER")
// .requestMatchers("/admins/**").permitAll()
// .requestMatchers("/login").permitAll()
//auth.requestMatchers("/instructors/**").hasRole("MANAGER")
auth.anyRequest().permitAll()
;
});
return http.build();
}
@Bean
public AuthenticationManager authenticationManager() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setPasswordEncoder(passwordEncoder());
provider.setUserDetailsService(loginService);
return new ProviderManager(provider);
}
@Bean
public LoginSuccessHandler loginSuccessHandler() {
return new LoginSuccessHandler(jwtService, adminRepository);
}
@Bean
public LoginFailureHandler loginFailureHandler() {
return new LoginFailureHandler();
}
@Bean
public CustomJsonUsernamePasswordAuthenticationFilter customJsonUsernamePasswordAuthenticationFilter() {
CustomJsonUsernamePasswordAuthenticationFilter customJsonUsernamePasswordLoginFilter
= new CustomJsonUsernamePasswordAuthenticationFilter(objectMapper);
customJsonUsernamePasswordLoginFilter.setAuthenticationManager(authenticationManager());
customJsonUsernamePasswordLoginFilter.setAuthenticationSuccessHandler(loginSuccessHandler());
customJsonUsernamePasswordLoginFilter.setAuthenticationFailureHandler(loginFailureHandler());
return customJsonUsernamePasswordLoginFilter;
}
@Bean
public JwtAuthenticationProcessingFilter jwtAuthenticationProcessingFilter() {
JwtAuthenticationProcessingFilter jwtAuthenticationFilter = new JwtAuthenticationProcessingFilter(jwtService, adminRepository);
return jwtAuthenticationFilter;
}
}
@RequiredArgsConstructor
@Slf4j
public class JwtAuthenticationProcessingFilter extends OncePerRequestFilter {
private static final String NO_CHECK_URL = "/login";
private final JwtService jwtService;
private final AdminRepository adminRepository;
private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if (request.getRequestURI().equals(NO_CHECK_URL)) {
filterChain.doFilter(request, response);
return;
}
String refreshToken = jwtService.extractRefreshToken(request)
.filter(jwtService::isTokenValid)
.orElse(null);
if (refreshToken != null) {
checkRefreshTokenAndReIssueAccessToken(response, refreshToken);
return;
}
if (refreshToken == null) {
checkAccessTokenAndAuthentication(request, response, filterChain);
}
}
public void checkRefreshTokenAndReIssueAccessToken(HttpServletResponse response, String refreshToken) {
adminRepository.findByRefreshToken(refreshToken)
.ifPresent(admin -> {
String reIssuedRefreshToken = reIssueRefreshToken(admin);
jwtService.sendAccessAndRefreshToken(response, jwtService.createAccessToken(admin.getEmail()),
reIssuedRefreshToken);
});
}
private String reIssueRefreshToken(Admin admin) {
String reIssuedRefreshToken = jwtService.createRefreshToken();
admin.updateRefreshToken(reIssuedRefreshToken);
adminRepository.saveAndFlush(admin);
return reIssuedRefreshToken;
}
public void checkAccessTokenAndAuthentication(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
log.info("checkAccessTokenAndAuthentication() 호출");
jwtService.extractAccessToken(request)
.filter(jwtService::isTokenValid)
.ifPresent(accessToken -> jwtService.extractEmail(accessToken)
.ifPresent(email -> adminRepository.findByEmail(email)
.ifPresent(this::saveAuthentication)));
log.info("saveAuthentication() 호출");
filterChain.doFilter(request, response);
}
public void saveAuthentication(Admin myAdmin) {
String password = myAdmin.getPassword();
UserDetails userDetailsUser = org.springframework.security.core.userdetails.User.builder()
.username(myAdmin.getEmail())
.password(password)
.roles(myAdmin.getRole().name())
.build();
Authentication authentication =
new UsernamePasswordAuthenticationToken(userDetailsUser, null,
authoritiesMapper.mapAuthorities(userDetailsUser.getAuthorities()));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
@RequiredArgsConstructor
@RequestMapping("/instructors")
@RestController
public class InstructorController {
private final InstructorService instructorService;
@PostMapping
@PreAuthorize("hasRole('ROLE_MANAGER')")//it works
public ResponseEntity createInstructor(@Valid @RequestBody InstructorPostDto instructorPostDto) {
InstructorResponseDto createdInstructor = instructorService.createInstructor(instructorPostDto);
return new ResponseEntity<>(createdInstructor, HttpStatus.CREATED);
}
plugins {
id 'java'
id 'org.springframework.boot' version '3.1.7'
id 'io.spring.dependency-management' version '1.1.4'
}
group = 'com.hh99'
version = '0.0.1-SNAPSHOT'
java {
sourceCompatibility = '17'
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
// security 관련 의존성
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
// jwt 관련 의존성
implementation 'com.auth0:java-jwt:4.2.1'
}
tasks.named('bootBuildImage') {
builder = 'paketobuildpacks/builder-jammy-base:latest'
}
tasks.named('test') {
useJUnitPlatform()
}
Hi I'm trying to apply Spring Security+JWT on my small application and I wanted that only 'MANAGER' can access to /lectures,/instructors but even though user has MANAGER authorization, they couldn't access to them so I changed hasAnyRole("MANAGER") to permitAll() and applied @PreAuthorized on Controller, and then it works maybe the order of filters or just problem of code.. can anyone help me??
There are two ways to solve this
You may use hasAuthority instead of hasRole
auth.requestMatchers("/lectures/**").hasAuthority("MANAGER")
Authorities should prefix role with the "ROLE_". You may modify getAuthorities() method in your entity class extending org.springframework.security.core.userdetails.UserDetails class as follows
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of(new SimpleGrantedAuthority("ROLE_"+role.name()));
}
difference-between-role-and-grantedauthority-in-spring-security here is the link which has explained this concept in detail.