Spring Boot MVC with JWT Token - spring

I am trying to build a web app with custom login page and access roles, but I wanna include JWT token also for the authentication and authorization.
Before including JWT, I configured security with access roles and ant matchers and custom login, I am able to access index view, when i wanna access "List of Students" i have to login with Admin credentials, when i wanna access "List of Subjects" i have to login with User credentials and it all works.
Now i wanna include JWT and i have all the JWT dependencies enabled, JwtRequest class, JwtResponse class, JwtUtil class and JwtRequestFilter. My security configuration is:
csrf().disable()
.authorizeRequests()
.antMatchers("/","/registration/**","/logout","/login").permitAll()
.antMatchers("/students/**").hasRole("Admin")
.antMatchers("/subjects/**").hasAnyRole("User","Admin")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.and()
.logout()
.logoutSuccessUrl("/login?logout");
and i also included:
// http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
//
// http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
I have my CustomUserDetailsClass:
public class CustomUserDetails implements UserDetails {
private User user;
public CustomUserDetails(User user) {
this.user = user;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities()
{
List<Role> roles = user.getRoles();
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
for(Role role : roles)
{
authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getName()));
}
return authorities;
}
#Override
public String getPassword() {
return user.getPassword();
}
#Override
public String getUsername() {
return user.getEmail();
}
I have my CustomUserDetailsService:
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
User user = userRepository.findByEmail(email);
if(user == null)
{
throw new UsernameNotFoundException("User Not Found!");
}
return new CustomUserDetails(user);
}
I am not finding a solution to implement Token with my views. I can do it using postman with #RestController using a "/authenticate" method.
THanks in advance!
I wanna build an web app with user and admin. Users can access "list of subjects" and admin can access "list of students".
But im not being able to implement the JWT token with my WEb app

I had this kind of scenario in one of my projects, I uploaded it on GitHub so you can have a look, I fell it exactly what you want.
You just have to create a Authentication filter like in this project.
https://github.com/Parneet-Raghuvanshi/Spring-Boot-Basics
Do watch the security folder, it's all in there what you need.
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and()
.csrf().disable().authorizeRequests()
.antMatchers(HttpMethod.GET, "/customer").hasRole("CUSTOMER")
.antMatchers(HttpMethod.GET, "/admin").hasRole("ADMIN")
.antMatchers(HttpMethod.GET, SecurityConstants.TEST_URL).permitAll()
.antMatchers(HttpMethod.GET, SecurityConstants.VERIFICATION_EMAIL_URL).permitAll()
.antMatchers(HttpMethod.POST, SecurityConstants.SIGNUP_URL).permitAll()
.antMatchers(HttpMethod.POST, SecurityConstants.PASSWORD_URL).permitAll()
.antMatchers(HttpMethod.POST, SecurityConstants.PASSWORD_RESET_URL).permitAll()
.anyRequest().authenticated()
.and().addFilter(getAuthenticationFilter())
.addFilter(new JwtFilter(authenticationManager()))
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
this is the configure() function of my web security from same project, so that you can have an idea about the filters used here.
And feel free to ask any doubt from repo too. Good Luck :)

Related

Request method 'POST' is not supported

I'm trying to upgrade Spring Boot from 2.7.6 to 3.0.1. I have a problem during the login action. The following is my new WebSecurityConfig:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig {
private final CustomUserDetailsService customUserDetailsService;
private final CustomizeAuthenticationSuccessHandler customizeAuthenticationSuccessHandler;
public WebSecurityConfig(CustomUserDetailsService customUserDetailsService, CustomizeAuthenticationSuccessHandler customizeAuthenticationSuccessHandler) {
this.customUserDetailsService = customUserDetailsService;
this.customizeAuthenticationSuccessHandler = customizeAuthenticationSuccessHandler;
}
#Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(customUserDetailsService);
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public AccessDeniedHandler accessDeniedHandler(){
return new CustomAccessDeniedHandler();
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests()
.requestMatchers("/").permitAll()
.requestMatchers("/login").permitAll()
.authenticated()
.and()
.csrf().disable()
.formLogin()
.successHandler(customizeAuthenticationSuccessHandler)
.loginPage("/login")
.failureUrl("/login?error=true")
.usernameParameter("email")
.passwordParameter("password")
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.invalidateHttpSession(true)
.logoutSuccessUrl("/login?logout=true")
.and()
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler())
.and()
.authenticationProvider(authenticationProvider());
http
.sessionManagement()
.maximumSessions(1)
.expiredUrl("/login?expired=true");
return http.build();
}
// This second filter chain will secure the static resources without reading the SecurityContext from the session.
#Bean
#Order(0)
SecurityFilterChain resources(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/resources/**", "/static/**", "/css/**", "/js/**", "/images/**").permitAll()
.anyRequest().permitAll())
.requestCache().disable()
.securityContext().disable()
.sessionManagement().disable();
return http.build();
}
}
Follow my CustomUserDetailService:
#Service
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
public CustomUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User findUserByEmail(String email) {
System.out.println(email);
User user = userRepository.findByEmail(email.toLowerCase());
System.out.println(user.getEmail());
return userRepository.findByEmail(email.toLowerCase());
}
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
User user = userRepository.findByEmail(email.toLowerCase());
if (user != null) {
List<GrantedAuthority> authorities = Arrays.asList(new SimpleGrantedAuthority( user.getRole()));;
return buildUserForAuthentication(user, authorities);
} else {
throw new UsernameNotFoundException("username not found");
}
}
private UserDetails buildUserForAuthentication(User user, List<GrantedAuthority> authorities) {
return new org.springframework.security.core.userdetails.User(user.getEmail(), user.getPassword(), authorities);
}
}
When I run the application I see the login page, but when I enter the credential and press submit I receive the error:
Resolved [org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' is not supported]
and Tomcat shows:
HTTP Status 405 – Method Not Allowed Type Status Report
Message Method 'POST' is not supported.
I searched for a solution but really I don't understand where is the problem.
To use multiple HttpSecurity instances, you must specify a security matcher, otherwise the first SecurityFilterChain will process all requests, and no requests will reach the second chain.
See this section of the Spring Security reference documentation.
In your case the SecurityFilterChain called resources is matching all requests, because you don't have a security matcher.
Since the resources chain does not configure formLogin then Spring Security does not create the default /login POST endpoint.
You can fix this by changing requests to:
#Bean
#Order(0)
SecurityFilterChain resources(HttpSecurity http) throws Exception {
http
.securityMatchers((matchers) -> matchers
.requestMatchers("/resources/**", "/static/**", "/css/**", "/js/**", "/images/**") // the requests that this SecurityFilterChain will process
)
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().permitAll())
.requestCache().disable()
.securityContext().disable()
.sessionManagement().disable();
return http.build();
}
If you want more details on the difference between authorizeHttpRequests and requestMatchers you can check out this question.
This error typically occurs when the method in the controller is not mapped to a post request. Should be something like:
#RequestMapping(value = "/login", method = {RequestMethod.GET, RequestMethod.POST})
public ModelAndView login(...

Spring Boot / Spring Security role based authorization not working properly [duplicate]

This question already has answers here:
Springboot Security hasRole not working
(3 answers)
Closed 1 year ago.
I am trying to learn Spring Security with Spring Boot. I have a demo where I am implementing it. It works fine with login page and profile method which can be authenticate by any valid user. But when I am trying to access for a specific role then it does not work and gives me a "403 - access denied".
My access point >>
#Controller
public class HomeController {
#RequestMapping("/")
public String home() {
return "/home.jsp";
}
#RequestMapping("/profile")
public String profile() {
return "/profile.jsp";
}
#RequestMapping("/admin")
public String admin() {
return "/admin.jsp";
}
#RequestMapping("/management")
public String management() {
return "/management.jsp";
}
}
My configure method >>
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/login", "/").permitAll()
.antMatchers("/profile").authenticated()
.antMatchers("/admin").hasRole("ADMIN")
.antMatchers("/management").hasAnyRole("ADMIN", "MANAGEMENT")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login").permitAll()
.and()
.logout().invalidateHttpSession(true)
.clearAuthentication(true)
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/logout-success").permitAll();
}
My role assigned >>
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.singleton(new SimpleGrantedAuthority("ADMIN"));
}
I think that's the most unobvious thing about Spring Security. Roles and authorities are the same things but roles should be prefixed with ROLE_. So, the correct usage is
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.singleton(new SimpleGrantedAuthority("ROLE_ADMIN"));
}

Spring Security basic auth for REST Api single login needed?

I am using Spring Security basic auth for my REST API.
Initially I get unauthorized HTTP response status for unauthenticated secured routes.
If I provide the right credentials, I get a Ok HTTP response status, but after a single successful login, I can access all the secured routes without providing user credentials.
Here are my questions:
Is it the correct behaviour for basic authentication?
Why it happens?
My security config:
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserService userService;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userService);
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
//J-
http.csrf().disable()
.authorizeRequests()
.antMatchers("/save")
.permitAll()
.and()
.authorizeRequests()
.antMatchers("/h2-console/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.httpBasic();
//J+
//adding support for h2 console, otherwise crashes
http.headers().frameOptions().disable();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(authenticationProvider());
}
}
and here is the UserDetailsService's loadByUsername() method:
#Override
public UserDetails loadUserByUsername(String username) {
User user = userRepository.findByUsername(username);
if (user == null) {
throw new UserNotFoundException(username);
} else if (UserStatus.Deactivated.equals(user.getStatus())) {
throw new UserDeactivatedException(username);
}
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), Collections.singleton(new SimpleGrantedAuthority("USER")));
}
https://www.baeldung.com/spring-security-session
Refer mentioned link. For Restful API’s use stateless session policy

Spring boot security using userDetailsService

#Service
public class UserDetService implements UserDetailsService{
#Autowired
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
User user = userRepository.findByUserName(userName);
if(user == null){
throw new UsernameNotFoundException(userName);
}
return new UserPrincipal(user);
}
}
SecConfig.java
#Configuration
#EnableWebSecurity
public class SecConfig extends WebSecurityConfigurerAdapter{
#Bean
public UserDetailsService userDetailsService(){
return new UserDetService();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
auth.userDetailsService(userDetailsService());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/hello").access("hasRole('ROLE_ADMIN')")
.anyRequest().permitAll()
.and()
.formLogin().loginPage("/login")
.usernameParameter("username").passwordParameter("password")
.and()
.logout().logoutSuccessUrl("/login?logout")
.and()
.exceptionHandling().accessDeniedPage("/403")
.and()
.csrf();
}
}
I was learning spring security and didn't find anyone to ask if I was correct and is this all to do to secure login authentication or have I missed something?
Also I got a confusion that I haven't written any queries to get username and password, how does the validation of username and password from user input and database work and where the validation occur ?
Thank you for such a helpful people around here
You don't need to write queries because if you are using jpa it will write the queries for you.
This line: User user = userRepository.findByUserName(userName);
Will use the repository to auto-generate the query to check if the supplied parameter returns anything and in your service
if(user == null){
throw new UsernameNotFoundException(userName);
}
will throw the error if the repository couldn't find the user.
Hope this answers your question.

Spring Security custom AuthenticationProvider authenticate method called twice

I am developing a Spring Boot that uses an API Key to authenticate. I have created a custom Authentication provider and the authenticate method is called twice. Can anyone tell me why it's being called twice?
This is my authenticate method:
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
ApiAuthenticationToken authenticationToken = (ApiAuthenticationToken) authentication;
/**
* Authenticate the token
*/
ValidateApiKeyRequest request = new ValidateApiKeyRequest(authenticationToken.getApiKey());
ValidateApiKeyResp resp = getValidateApiKeyCommand().execute(request);
/**
* Populate and return a new authenticaiton token
*/
return createSuccessAuthentication(resp);
}
and this is the createSuccessAuthentication method:
protected Authentication createSuccessAuthentication(final ValidateApiKeyResp resp) {
List<GrantedAuthority> authorities = Lists.newArrayList();
authorities.add(new SimpleGrantedAuthority("API_KEY"));
return new ApiAuthenticationToken(resp.getApiKey(), authorities, true);
}
this is the ApiAuthenticationToken constructor:
public ApiAuthenticationToken(final ApiKey apiKey, Collection<? extends GrantedAuthority> authorities, boolean authenticated) {
super(authorities);
setAuthenticated(true);
this.apiKey = apiKey;
}
This is my security configuration:
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher(CONFIGURATION_MATCHER)
.exceptionHandling().authenticationEntryPoint(restAuthenticationEntryPoint())
.and()
.addFilterBefore(apiKeyAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.csrf().disable()
.authorizeRequests().antMatchers(CONFIGURATION_MATCHER).authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authenticationProvider(apiKeyAuthenticationProvider());
Just in case anyone else has this issue:
The problem was related to my spring security configuration. I had several methods annotated with #Bean - see below
#Bean
public ApiKeyAuthenticationProvider apiKeyAuthenticationProvider() {
return new ApiKeyAuthenticationProvider(getValidateApiKeyCommand());
}
#Bean
public RestAuthenticationEntryPoint restAuthenticationEntryPoint() {
return new RestAuthenticationEntryPoint();
}
#Bean
public ApiKeyAuthenticationFilter apiKeyAuthenticationFilter() throws Exception {
ApiKeyAuthenticationFilter apiKeyAuthenticationFilter = new ApiKeyAuthenticationFilter();
apiKeyAuthenticationFilter.setAuthenticationManager(authenticationManagerBean());
apiKeyAuthenticationFilter.setAuthenticationSuccessHandler(new ApiKeyAuthenticationSuccessHandler());
apiKeyAuthenticationFilter.setAuthenticationFailureHandler(new ApiKeyAuthenticationFailureHandler());
return apiKeyAuthenticationFilter;
}
But theses beans were getting registered again in the configure(HttpSecurity http) method.
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher(CONFIGURATION_MATCHER)
.exceptionHandling().authenticationEntryPoint(restAuthenticationEntryPoint())
.and()
.addFilterBefore(apiKeyAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.csrf().disable()
.authorizeRequests().antMatchers(CONFIGURATION_MATCHER).authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authenticationProvider(apiKeyAuthenticationProvider());
}
The fix was to remove the #Bean annotation. Seems obvious now :)

Resources