I'm learning Spring and now trying to configure security. I need to create secured endpoints and one not secured for registration.
But when I'm trying to access http://localhost:8080/register I'm getting error "An Authentication object was not found in the SecurityContext" and "AuthenticationCredentialsNotFoundException".
I have repeated example in docs but still get this error.
WebSecurityConfig.java
#Configuration
#EnableWebSecurity
#EnableResourceServer
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/register").permitAll();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
OAuth2Config.java
#Configuration
#EnableAuthorizationServer
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
#Autowired
#Qualifier("userDetailsService")
private UserDetailsService userDetailsService;
#Autowired
private AuthenticationManager authenticationManager;
#Value("3600")
private int expiration;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer configurer) throws Exception {
configurer.authenticationManager(authenticationManager);
configurer.userDetailsService(userDetailsService);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient("app").secret("secret").accessTokenValiditySeconds(expiration)
.scopes("read", "write").authorizedGrantTypes("password", "refresh_token").resourceIds("resource");
}
}
RegisterController.java
#RestController
public class RegisterController {
#Autowired
UserDao userDao;
#Autowired
CityDao cityDao;
#RequestMapping(value = "/register", method = RequestMethod.POST)
public ResponseEntity<?> addUser(#RequestParam(value = "email") String email, #RequestParam(value = "firstName") String firstName,
#RequestParam(value = "lastName") String lastName, #RequestParam(value = "city") Long cityId,
#RequestParam(value = "password") String password){
User userToFind = userDao.findByEmail(email);
City city = cityDao.findById(cityId).get();
if (userToFind != null) {
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
User user = new User(firstName, lastName, email, city, passwordEncoder.encode(password));
return new ResponseEntity<>(userDao.save(user), HttpStatus.CREATED);
}
else{
return new ResponseEntity<>(HttpStatus.CONFLICT);
}
}
}
Related
I added Username and password field to an existing Customer entity. I have added a Custom JWT filter and authentication provider with a WebSecurityConfig annotated #Order(2). But when I send a post request with the Username and password payload , I get an Unauthorized Error 401 response But no error message in Server Log I have checked the WebConfig file appropriately .What am I not getting right ? I've been on this all day and I seem to be frustrated already.
This is UserDetails class
public class MyCustomerDetails implements UserDetails{
/**
*
*/
private static final long serialVersionUID = -5087929420394311276L;
private Long id;
private String username;
private String password;
public MyCustomerDetails() {
}
public MyCustomerDetails(Long id, String username, String password) {
super();
this.id = id;
this.username = username;
this.password = password;
}
public static MyCustomerDetails build(Customer customer) {
return new MyCustomerDetails(customer.getId(),
customer.getUserName(), customer.getPassword());
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// TODO Auto-generated method stub
return null;
}
#Override
public String getPassword() {
// TODO Auto-generated method stub
return password;
}
#Override
public String getUsername() {
// TODO Auto-generated method stub
return username;
}
............
This is the Controller class
#CrossOrigin(origins = {"http://localhost:3000"})
#RestController
public class CustomerController {
#Autowired
CustomerAccountService customerRepo;
#Autowired
private CustomerJwtTokenUtil customerJwtTokenUtil;
#Autowired
private AuthenticationManager authenticationManager;
#PostMapping(value="/validateCustomer")
public ResponseEntity <?> createAuthenticationToken( #RequestBody MyCustomerDetails
authenticationRequest) throws Exception
{
authenticate(authenticationRequest.getUsername(),
authenticationRequest.getPassword());
// Long userId = authenticationRequest.getId();
final MyCustomerDetails userDetails =
(MyCustomerDetails) customerRepo.loadUserByUsername(authenticationRequest.getUsername());
final String token =
customerJwtTokenUtil.generateToken(userDetails);
return new ResponseEntity<>(new JwtResponse(token), HttpStatus.OK) ;
}
private void authenticate(String username, String password) throws Exception {
try {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
} catch (DisabledException e) {
throw new Exception("USER_DISABLED", e);
} catch (BadCredentialsException e) {
throw new Exception("INVALID_CREDENTIALS", e);
}
}
This is the WebSecurityconfig class
#Configuration
#Order(2)
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class CustomerSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private CustomerJwtAuthenticationEntryPoint customerJwtAuthenticationEntryPoint;
#Autowired
private UserDetailsService myCustomerDetailsService;
#Autowired
private CustomerJwtRequestFilter customerJwtRequestFilter;
#Bean
public CustomerAccountService myCustomerAccountService() {
return new CustomerAccountService();
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/*
* #Bean public UserDetailsService myCustomerDetailsService() { return
* myCustomerDetailsService(); }
*/
#Bean
public DaoAuthenticationProvider daoAuthenticationProvider(PasswordEncoder passwordEncoder,
UserDetailsService userDetailsService){
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder);
daoAuthenticationProvider.setUserDetailsService(userDetailsService);
return daoAuthenticationProvider;
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
protected void configure(AuthenticationManagerBuilder auth ) throws Exception {
auth.userDetailsService(myCustomerDetailsService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.authorizeRequests()
.antMatchers("/***").permitAll()
// .antMatchers("/customer/**").hasAuthority("CUSTOMER")
.anyRequest().authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(customerJwtAuthenticationEntryPoint)
.and()
.formLogin().permitAll()
// .loginPage("/login")
.and()
.logout().logoutUrl("/logout").logoutSuccessUrl("/login")
.and()
.sessionManagement()
.maximumSessions(1)
.and()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(customerJwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
}
The Service class
#Primary
public class CustomerAccountService implements UserDetailsService {
#Autowired
private CustomerAccountRepo custRepo;
#Qualifier("passwordEncoder")
#Autowired
private PasswordEncoder bCryptPasswordEncoder;
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Customer customer = custRepo.findByUserName(username);
if(customer == null) {
throw new UsernameNotFoundException("Customer not found");
}
return MyCustomerDetails.build(customer);
}
This is the Base class
#Configuration
#EnableWebMvc
//#ComponentScan(basePackages = "com.bethsaida.org.security")
#EnableJpaRepositories
#SpringBootApplication(exclude = { SecurityAutoConfiguration.class })
public class BethsaidaApplication {
public static void main(String[] args)
{SpringApplication.run(BethsaidaApplication.class, args);}
public class WebConfig implements WebMvcConfigurer
{
private static final long MAX_AGE_SECS = 3600;
#Override
public void addCorsMappings(CorsRegistry registry)
{ registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("HEAD", "OPTIONS", "GET", "POST", "PUT", "PATCH", "DELETE")
.maxAge(MAX_AGE_SECS);}
}
}
You can use this
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("POST", "PUT", "DELETE")
.allowedHeaders("header1", "header2")
.exposedHeaders("header1", "header2")
.allowCredentials(false).maxAge(3600);
}
inside your CustomerSecurityConfiguration class directly.
Because WebMvcConfigurer is implemented by WebMvcConfigurerAdapter so you can define this method inside your CustomerSecurityConfiguration class.
For more information, you can see here
So I am making my first spring boot project (I am making a RESTful backend API) and I came across jwt authentication. After trying a few tutorials I was keep getting stuck until one tutorial kinda helped me.
My problem:
When I run my AuthenticationController:
#RestController
#CrossOrigin
public class JwtAuthenticationController {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private JwtTokenUtil jwtTokenUtil;
#Autowired
private JwtUserDetailsService userDetailsService;
#RequestMapping(value = "/authenticate", method = RequestMethod.POST)
public ResponseEntity<?> createAuthenticationToken(#RequestBody JwtRequest authenticationRequest) throws Exception {
try {
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(authenticationRequest.getUsername(), authenticationRequest.getPassword())
);} catch (BadCredentialsException e){
throw new Exception("Incorrect username or password", e);
}
final UserDetails userDetails = userDetailsService
.loadUserByUsername(authenticationRequest.getUsername());
final String token = jwtTokenUtil.generateToken(userDetails);
return ResponseEntity.ok(new JwtResponse(token));
}
#RequestMapping(value = "/register", method = RequestMethod.POST)
public ResponseEntity<?> saveUser(#RequestBody UserDTO user) throws Exception {
return ResponseEntity.ok(userDetailsService.save(user));
}
}
My code does not run past this piece of code:
try {
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(authenticationRequest.getUsername(), authenticationRequest.getPassword())
);} catch (BadCredentialsException e){
throw new Exception("Incorrect username or password", e);
}
I know this because of debugging basically. It always goes to the catch and I don't know why.
When I comment out that not working piece of code, my code runs but I can use any username and password with the given jwt Token, and that is not what I want.
So I know the piece of code that makes sure my program does not run as planned is that piece.
I know the not working piece of code uses my JwtRequest so maybe anyone needs that to help me fix my problem:
JwtRequest:
package com.example.demo.Model;
public class JwtRequest{
private String username;
private String password;
public JwtRequest() {
}
public JwtRequest(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
Here is my debugger and database to confirm that the credentials are correct:
My debugger:
My DataBase:
My Postman:
What I have tried:
I have tried replacing this code:
try {
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(authenticationRequest.getUsername(), authenticationRequest.getPassword())
);} catch (BadCredentialsException e){
throw new Exception("Incorrect username or password", e);
}
With this:
authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
And my according authenticate method:
private void authenticate(String username, String password) throws Exception {
try {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
} catch (DisabledException e) {
throw new Exception("USER_DISABLED", e);
} catch (BadCredentialsException e) {
throw new Exception("INVALID_CREDENTIALS", e);
}
}
But then my debugger says this:
I am basically stuck and I do not know what to do now. Can anyone help me or provide me with a tutorial on how to implement JWT authentication with an MYSQL database that is not outdated and has the source code included in the tutorial?
Thanks!
If you need some piece of code just ask and I will include it in the post.
As requested the following files:
WebSecurityConfig:
#Configuration
#AllArgsConstructor
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {//provides security for endpoints
#Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
#Autowired
private UserDetailsService jwtUserDetailsService;
#Autowired
private JwtRequestFilter jwtRequestFilter;
private final AccountService accountService;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
// configure AuthenticationManager so that it knows from where to load
// user for matching credentials
// Use BCryptPasswordEncoder
auth.userDetailsService(jwtUserDetailsService).passwordEncoder(bCryptPasswordEncoder);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()//So we can send post requests without being rejected(if we using form based indication we want to enable this)
.authorizeRequests()
.antMatchers("/authenticate","/register")
.permitAll()//any request that goes trough that end point we want to allow.
.anyRequest()
.authenticated().and().exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(daoAuthenticationProvider());
}
#Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider provider =
new DaoAuthenticationProvider();
provider.setPasswordEncoder(bCryptPasswordEncoder);
provider.setUserDetailsService(accountService);
return provider;
}
}
Password encoder:
#Configuration
public class PasswordEncoder{
#Bean
public BCryptPasswordEncoder bCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
}
}
UserdetailsService:
#Service
public class JwtUserDetailsService implements UserDetailsService {
#Autowired
private UserDao userDao;
#Autowired
private PasswordEncoder bcryptEncoder;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
DAOUser user = userDao.findByUsername(username);
if (user == null){
throw new UsernameNotFoundException("User not found with username: " + username);
}
return new User(user.getUsername(), user.getPassword(), new ArrayList<>());
}
public DAOUser save(UserDTO user) {
DAOUser newUser = new DAOUser();
newUser.setUsername(user.getUsername());
newUser.setPassword(bcryptEncoder.bCryptPasswordEncoder().encode(user.getPassword()));
return userDao.save(newUser);
}
}
#Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider provider =
new DaoAuthenticationProvider();
provider.setPasswordEncoder(bCryptPasswordEncoder);
provider.setUserDetailsService(accountService);
return provider;
}
You are setting accountService as userDetailsService instead of `JwtUserDetailsService'. have you tried that?
change in JwtUserDetailsService.java
//not sure why accountService-> don't make it final
//#Autowired //missing
//private AccountService accountService;
#Autowired
private PasswordEncoder bcryptEncoder;
#Configuration
#AllArgsConstructor
//#EnableWebSecurity not needed
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {//provides security for endpoints
#Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
#Autowired
private JwtUserDetailsService jwtUserDetailsService;
#Autowired
private JwtRequestFilter jwtRequestFilter;
//private AccountService accountService;
#Autowired
private PasswordEncoder bCryptPasswordEncoder;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
// configure AuthenticationManager so that it knows from where to load
// user for matching credentials
// Use BCryptPasswordEncoder
auth.userDetailsService(jwtUserDetailsService).passwordEncoder(bCryptPasswordEncoder);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()//So we can send post requests without being rejected(if we using form based indication we want to enable this)
.authorizeRequests()
.antMatchers("/authenticate","/register")
.permitAll()//any request that goes trough that end point we want to allow.
.anyRequest()
.authenticated().and().exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
//.and().addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class); You are not using filter to authenticate, you are using a controller for that
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(daoAuthenticationProvider());
}
#Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider provider =
new DaoAuthenticationProvider();
provider.setPasswordEncoder(bCryptPasswordEncoder);
//provider.setUserDetailsService(accountService);
return provider;
}
}
I am trying to test my Spring Application which consists of JWT auth with Junit and Mockmvc. Signup test is working as expected. But when trying to test login, StackOverflowError is displayed. Inside the JWTAuthenticationFilter.java, data is being successfully received. But after that error is displayed. Please help me out. Thanks!
Error:
java.lang.StackOverflowError
at org.mockito.internal.invocation.MatchersBinder.bindMatchers(MatchersBinder.java:25)
at org.mockito.internal.handler.MockHandlerImpl.handle(MockHandlerImpl.java:59)
at org.mockito.internal.handler.NullResultGuardian.handle(NullResultGuardian.java:29)
at org.mockito.internal.handler.InvocationNotifierHandler.handle(InvocationNotifierHandler.java:35)
at org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.doIntercept(MockMethodInterceptor.java:63)
at org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.doIntercept(MockMethodInterceptor.java:49)
at org.mockito.internal.creation.bytebuddy.MockMethodInterceptor$DispatcherDefaultingToRealMethod.interceptSuperCallable(MockMethodInterceptor.java:110)
at org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder$MockitoMock$310380589.encode(Unknown Source)
at org.springframework.security.authentication.dao.DaoAuthenticationProvider.prepareTimingAttackProtection(DaoAuthenticationProvider.java:142)
at org.springframework.security.authentication.dao.DaoAuthenticationProvider.retrieveUser(DaoAuthenticationProvider.java:106)
at org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.authenticate(AbstractUserDetailsAuthenticationProvider.java:144)
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:175)
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:200)
at org.springframework.security.con
WebSecurity.java
#EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
private UserDetailsServiceImpl userDetailsService;
private BCryptPasswordEncoder bCryptPasswordEncoder;
public WebSecurity(UserDetailsServiceImpl userDetailsService, BCryptPasswordEncoder bCryptPasswordEncoder) {
this.userDetailsService = userDetailsService;
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable().authorizeRequests()
.antMatchers(HttpMethod.POST, SecurityConstants.SIGN_UP_URL).permitAll()
.anyRequest().authenticated()
.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager()))
// this disables session creation on Spring Security
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception{
return super.authenticationManagerBean();
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.parentAuthenticationManager(authenticationManagerBean())
.userDetailsService(userDetailsService)
.passwordEncoder(bCryptPasswordEncoder);
}
}
UserControllerTest.java
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
#AutoConfigureJsonTesters
public class UserControllerTest {
#Autowired
private MockMvc mvc;
#Autowired
private JacksonTester<User> json;
#Autowired
private JacksonTester<CreateUserRequest> jsonN;
#MockBean
private UserRepository userRepository;
#MockBean
private BCryptPasswordEncoder encoder;
private CreateUserRequest r;
#Before
public void setup(){
r = new CreateUserRequest();
r.setUsername("ujjwal2102");
r.setPassword("ujjwal21");
r.setConfirmPassword("ujjwal21");
}
#Test
public void createUserTest() throws Exception{
signup();
}
#Test
public void loginUserTest() throws Exception{
signup();
login();
}
public void signup() throws Exception{
when(encoder.encode("ujjwal21")).thenReturn("ujjwal21");
mvc.perform(
post(new URI("/api/user/create"))
.content(jsonN.write(r).getJson())
.contentType(MediaType.APPLICATION_JSON_UTF8)
.accept(MediaType.APPLICATION_JSON_UTF8))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id",is(0)))
.andExpect(jsonPath("$.username",is("ujjwal2102")));
}
public void login() throws Exception{
User user = new User();
user.setUsername("ujjwal2102");
user.setPassword("ujjwal21");
when(encoder.encode("ujjwal21")).thenReturn("ujjwal21");
mvc.perform(
post(new URI("/login"))
.content(json.write(user).getJson())
.contentType(MediaType.APPLICATION_JSON_UTF8)
.accept(MediaType.APPLICATION_JSON_UTF8))
.andExpect(status().isOk());
}
}
User.java
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#JsonProperty
private long id;
#Column(nullable = false, unique = true)
#JsonProperty
private String username;
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
#Column(nullable = false)
private String password;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "cart_id", referencedColumnName = "id")
#JsonIgnore
private Cart cart;
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Cart getCart() {
return cart;
}
public void setCart(Cart cart) {
this.cart = cart;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
JWTAuthenticationFilter.java
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest req,
HttpServletResponse res) throws AuthenticationException {
try {
User creds = new ObjectMapper()
.readValue(req.getInputStream(), User.class);
System.out.println("USERNAME-----" + creds.getUsername());
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
creds.getUsername(),
creds.getPassword(),
new ArrayList<>())
);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
#Override
protected void successfulAuthentication(HttpServletRequest req,
HttpServletResponse res,
FilterChain chain,
Authentication auth) throws IOException, ServletException {
String token = JWT.create()
.withSubject(((org.springframework.security.core.userdetails.User) auth.getPrincipal()).getUsername())
.withExpiresAt(new Date(System.currentTimeMillis() + SecurityConstants.EXPIRATION_TIME))
.sign(HMAC512(SecurityConstants.SECRET.getBytes()));
System.out.println("TOKEN----" + token);
res.addHeader(SecurityConstants.HEADER_STRING, SecurityConstants.TOKEN_PREFIX + token);
}
}
I noticed one mistake that may or may not solved your issue, but may compromise your authentification process.
new UsernamePasswordAuthenticationToken(
creds.getUsername(),
creds.getPassword(),
new ArrayList<>()) // with autorities, the user is authenticated
new UsernamePasswordAuthenticationToken(
creds.getUsername(),
creds.getPassword()) // without autorities, the user is not authenticated
The constructor with autorities is usually used by the AuthentificationManager after authentification is successful. The constructor without is used by the filter to pass to the AuthentificationManager.
Spring Security Javadoc
Looking at the log, the issue looks to be in your Spring Security Configuration that you didn't provide. You may have done a recursive call inside an authentification provider.
I understand that Spring will pass the principal into controller's method if I include it as a method parameter.
I'm trying to extend this functionality by implementing UserDetailsService:
I created a class named CustomUserDetails that extends org.springframework.security.core.userdetails.User
I created a service named CustomUserDetailsService that implements UserDetailsService
Exception
HTTP Status 500 - Request processing failed; nested exception is
java.lang.ClassCastException:
org.springframework.security.authentication.UsernamePasswordAuthenticationToken
cannot be cast to com.demo.model.CustomUserDetails
The following line in my controller method is throwing the exception:
CustomUserDetails userDetails = (CustomUserDetails) principal;
Controller.java
#RequestMapping(value = "/dashboard", method = RequestMethod.GET)
public ModelAndView displayHomePage(ModelAndView modelAndView, Principal principal, HttpServletRequest request) {
// Throws exception here
CustomUserDetails userDetails = (CustomUserDetails) principal;
System.out.println(userDetails.getFirstName());
// Tried this and it also throws exception
// User cannot be cast to CustomUserDetails
//Authentication auth = SecurityContextHolder.getContext().getAuthentication();
//CustomUserDetails userDetails = (CustomUserDetails)auth.getPrincipal();
// Render template located at
// src/main/resources/templates/dashboard.html
modelAndView.setViewName("dashboard");
return modelAndView;
}
SecurityConfiguration.java
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
#Autowired
private DataSource dataSource;
#Value("${spring.queries.users-query}")
private String usersQuery;
#Value("${spring.queries.roles-query}")
private String rolesQuery;
#Autowired
SecurityHandler successHandler;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().usersByUsernameQuery(usersQuery).authoritiesByUsernameQuery(rolesQuery)
.dataSource(dataSource).passwordEncoder(bCryptPasswordEncoder);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/").permitAll().antMatchers("/register*").permitAll()
.antMatchers("/reset").permitAll().antMatchers("/forgot").permitAll().antMatchers("/grid").permitAll()
.antMatchers("/login").permitAll().antMatchers("/admin/**").hasAuthority("ADMIN").anyRequest()
.authenticated().and().formLogin().loginPage("/login").failureUrl("/login?error")
.defaultSuccessUrl("/dashboard").successHandler(successHandler).usernameParameter("email")
.passwordParameter("password").and().logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/login?logout").and().exceptionHandling().accessDeniedPage("/access-denied");
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/error**", "/resources/**", "/static/**", "/css/**", "/js/**", "/img/**");
}
}
CustomUserDetails.java
public class CustomUserDetails extends org.springframework.security.core.userdetails.User {
public CustomUserDetails(String username, String password,
Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
}
private String firstName;
private String lastName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
CustomUserDetailsService.java
#Service
public class CustomUserDetailsService implements UserDetailsService{
#Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException{
if(StringUtils.isEmpty(userName))
throw new UsernameNotFoundException("User name is empty");
//if you don't use authority based security, just add empty set
Set<GrantedAuthority> authorities = new HashSet<>();
CustomUserDetails userDetails = new CustomUserDetails(userName, "", authorities);
userDetails.setFirstName("Testing: " + new Date());
return userDetails;
}
}
in WebSecurityConfigurerAdapter, you need add your register custom detail service:
auth.userDetailsService(customDetailService)
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().usersByUsernameQuery(usersQuery).authoritiesByUsernameQuery(rolesQuery)
.dataSource(dataSource).passwordEncoder(bCryptPasswordEncoder);
}
Change your controller to:
CONTROLLER
#RequestMapping(value = "/dashboard", method = RequestMethod.GET)
public ModelAndView displayHomePage(ModelAndView modelAndView, Authentication authentication, HttpServletRequest request) {
CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal;
Note it returns a Authentication object.
I have implemented spring security in my application.
It is stateless-token based authentication and username/password based authentication.
I have configured user authentication, but the role-based authorisation is not working.
A user who has ROLE_USER is able to access the controller method which has ROLE_ADMIN.
Here is the configuration.
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true)
#Configuration
public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter{
#Bean
AuthenticationProvider passwordBasedAuthenticationProvider() {
return new PasswordBasedAuthenticationProvider();
}
#Bean
AuthenticationProvider tokenBasedAuthenticationProvider(){
return new TokenBasedAuthenticationProvider();
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/api/v1/public/**");
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.
csrf().disable().
sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).
and().
authorizeRequests().
anyRequest().authenticated().
and().
anonymous().disable();
http.addFilterBefore(new AuthenticationFilter(authenticationManager()), BasicAuthenticationFilter.class);
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(passwordBasedAuthenticationProvider()).
authenticationProvider(tokenBasedAuthenticationProvider());
}
}
DOMAINS
#Entity
public class Role implements GrantedAuthority {
private long id;
private String authority;
}
public class User implements UserDetails{
private String username;
private String passwordHash;
private Role role;
}
#RestController
public class TesController {
#RequestMapping(value="/authController")
#Secured("ROLE_ADMIN")
String test(){ return "I am secure for ROLE_ADMIN"}
}
What is incorrect about this configuration?
You have to define at least the RoleHierarchie with something like this or whatever the configuration may look like in your case:
#Bean
public RoleHierarchy roleHierarchy() {
RoleHierarchyImpl r = new RoleHierarchyImpl();
r.setHierarchy("ROLE_ADMIN > ROLE_STAFF");
r.setHierarchy("ROLE_STAFF > ROLE_USER");
r.setHierarchy("ROLE_DEVELOPER > ROLE_USER");
r.setHierarchy("ROLE_USER > ROLE_GUEST");
return r;
}