Issue
Here is my User Details Object
2022-10-19 07:35:58.143 INFO 24580 --- [nio-8081-exec-6] c.s.jwt.api.filter.JwtFilter : User Details: org.springframework.security.core.userdetails.User [Username=502553205, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[Authority(id=1, authority=Engine Line Tracker, description=Engine Line Tracker), Authority(id=2, authority=Pin a Card, description=Pin a Card), Authority(id=5, authority=Project Plan, description=Project Plan), Authority(id=4, authority=Scorecard, description=Scorecard), Authority(id=6, authority=Search, description=Search), Authority(id=3, authority=Upload/Download/Edit, description=Upload/Download/Edit)]]
My Endpoint is
@GetMapping
@RolesAllowed({"Scorecard"})
public List<User> list(){
return userRepository.findAll();
}
This runs when I remove @RolesAllowed but not taking roles from my user object. Am I doing sth wrong in placing roles?
Yes its enabled
package com.springimplant.jwt.api.config;
import javax.servlet.http.HttpServletResponse;
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.BeanIds;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.annotation.web.configuration.WebSecurityConfigurerAdapter;
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.authentication.UsernamePasswordAuthenticationFilter;
import com.springimplant.jwt.api.filter.JwtFilter;
import com.springimplant.jwt.api.service.CustomUserDetailService;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(
prePostEnabled = false,
securedEnabled = false,
jsr250Enabled = true
)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private CustomUserDetailService userDetailService;
@Autowired
private JwtFilter filter;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean(name= BeanIds.AUTHENTICATION_MANAGER)
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailService).passwordEncoder(new BCryptPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests().antMatchers("/authenticate").permitAll()
.anyRequest().authenticated().and()
.exceptionHandling().authenticationEntryPoint(((request, response, authException) -> {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
authException.getMessage();
} )).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(filter,UsernamePasswordAuthenticationFilter.class);
}
}
Also my Authority class is as follows
package com.springimplant.jwt.api.entity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "authorties")
public class Authority implements GrantedAuthority{
// private static final long serialVersionUID = 1L;
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
@Id
@SequenceGenerator(name = "authorties_seq", sequenceName = "authorties_seq")
@GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "authorties_seq")
private Long id;
@Column(name="authority")
private String authority;
@Column(name="description")
private String description;
@Override
public String getAuthority() {
return authority;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof Authority) {
return this.authority.equals(((Authority) obj).authority);
}
return false;
}
@Override
public int hashCode() {
return this.authority.hashCode();
}
@Override
public String toString() {
return this.authority;
}
}
Solution
First of all, I believe it's a good practice to make role names short without spaces, slashes, and underscored. Your list may look like this: SCORECARD, PROJECT_PLAN, UPLOAD_DOWNLOAD_EDIT.
To use @RolesAllowed you have to make sure you enabled jsr250 in @EnableGlobalMethodSecurity:
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {}
or for Spring 5.6 and later you can replace @EnableGlobalMethodSecurity annotation with @EnableMethodSecurity
This allows you to use @Secured, @PreAuthorized, @PostAuthorized and @RolesAllowed.
More info about it is in the official docs
===== UPD =====
Now, when you provided more details I can guess that you missed one important thing: role != authority. By default roles in Spring have ROLE_ prefix. If you didn't replace it anywhere it just tries to compare ROLE_Scorecard to Scorecard.
To remove that prefix you can do this in your config:
@Bean
fun grantedAuthorityDefaults(): GrantedAuthorityDefaults? {
return GrantedAuthorityDefaults("") // Remove the ROLE_ prefix
}
=== UPD2 ===
Ha, one more good point appeared from Ken Chan while I were writing the update. If you're going to use an authorities approach than Ken Chan's hasAuthority() fits even better.
Answered By - Victor Bjorn
Answer Checked By - Mildred Charles (JavaFixing Admin)