Why SecurityContextHolder.getContext() == null? - spring

I don't run my app, because auth == null. Why? I use spring boot 2.2.7.RELEASE and spring-security-oauth2-autoconfigure.
When starting SecurityContextHolder.getContext() always equal null
#Component
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
DataSource dataSource;
#Autowired
UserRepository userRepository;
#Autowired
PasswordEncoder passwordEncoder;
#Bean
public PasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/addPet").authenticated()
.antMatchers("/", "/**").access("permitAll")
.and().oauth2Login().loginPage("/login").defaultSuccessUrl("/", true).and().logout().logoutSuccessUrl("/")
.and().formLogin().loginPage("/login").loginProcessingUrl("/login").usernameParameter("login").passwordParameter("password")
.defaultSuccessUrl("/", true).and().logout().logoutSuccessUrl("/").deleteCookies("JSESSIONID").and().csrf().disable();
}
#Autowired
public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery(
"select login, password, enabled from users where login=?")
.authoritiesByUsernameQuery(
"select login, role from users where login=?");
}
}

I assume that you're referring to this line,
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
In this case, you don't need this line, it's not being used anywhere. In the bigger answer, SecurityContextHolder.getContext() will never be null, according to the documentation. The call to getAuthentication(), however, will be null unless there's an authenticated principal associated to the session.

Related

Configuring both http basic and Form based authentication in latest Spring security 6.0

I'm trying to configure REST and Form based authentication in new Spring boot and spring secuirty latest. I went through following post Combining basic authentication and form login for the same REST Api and new spring configuration using SecurityFilterChain and created the following. As I learnt that WebSecurityConfigurerAdapter method in the above post is deprecated.
#Configuration
#Order(2)
#EnableWebSecurity
public class RESTBasedConfigurationAdapter {
#Autowired
private AuthenticationProvider authenticationProvider;
#Bean
public BCryptPasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
#Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/**")
.authorizeRequests().anyRequest().hasAnyRole(...)
.and().httpBasic()
.authenticationEntryPoint(authenticationEntryPoint());
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
return http.build();
}
}
and
#Configuration
#Order(Ordered.HIGHEST_PRECEDENCE)
#EnableWebSecurity
public class FormBasedConfigurationAdapter {
#Autowired
private AuthenticationProvider authenticationProvider;
#Autowired
private AuthenticationSuccessHandler authenticationSuccessHandler;
#Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers(...)
.permitAll().anyRequest().authenticated()
.and().formLogin().loginPage("/login").successHandler(authenticationSuccessHandler)
.permitAll().and().logout().permitAll();
return http.build();
}
}
But the configure method FormBasedConfigurationAdapter's is never called. Please explain how to configure so that both http Basic and Form based Authentication can be done.
As far I under I want two flows.
One flow REST which uses the following AuthenticationProvider for logging STATELESS
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
#Lazy
private BCryptPasswordEncoder passwordEncoder;
#Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
String userName = authentication.getName();
String password = authentication.getCredentials().toString();
UserDetails userDetails = userDetailsService.loadUserByUsername(userName);
if (passwordEncoder.matches(password, userDetails.getPassword())) {
return new UsernamePasswordAuthenticationToken(userName, password, userDetails.getAuthorities());
} else {
throw new BadCredentialsException(" Bad Credentials ");
}
}
#Override
public boolean supports(Class<?> authenticationType) {
return authenticationType.equals(UsernamePasswordAuthenticationToken.class);
}
}
and for other FormBased authentication, I would like to go through controller as below.
#PostMapping("/login")
public String login(#ModelAttribute("loginForm") LoginForm loginForm,
BindingResult bindingResult,
Model model) {
loginValidator.validate(loginForm, bindingResult);
securityService.login(loginForm.getUserName(), loginForm.getPasswd());
if (bindingResult.hasErrors()) {
return "login";
}
return "...";
}
to facilitate Validation for Authenticate using Service as below.
#Override
public void login(String username, String password) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(userDetails, password, userDetails.getAuthorities());
if (passwordEncoder.matches(password, userDetails.getPassword())) {
if (usernamePasswordAuthenticationToken.isAuthenticated()) {
SecurityContextHolder.getContext()
.setAuthentication(usernamePasswordAuthenticationToken);
logger.debug(String.format("Auto login %s successfully!", username));
}
} else {
throw new BadCredentialsException(" Bad Credentials ");
}
}
Please explain how to achieve this. I also tried doing both HttpSecurity mapping in the same class but it is not working due to various reasons.

Why can't I go to admin REST API request mapping after I have logged in - Spring Boot

I have some issues with my REST API, created from Spring Boot and Spring Security.
First I have created my Spring security configuration. As you see here, I have two paths with two different authorizations - USER and ADMIN.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter{
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private UserRepository userRepository;
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// Set the default standard admin and password as admin, if not exist
User user = userRepository.findByUsername("admin");
if(user == null) {
user = new User();
user.setUserID(0);
user.setUsername("admin");
user.setPassword(passwordEncoder().encode("admin"));
Set<Role> roles = new HashSet<Role>();
Role role = new Role();
role.setRoleID(0);
role.setRolename("ADMIN");
roles.add(role);
user.setRoles(roles);
userRepository.save(user);
}
// Connect our database to spring and also with a password encoder
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests().antMatchers("/user/**").authenticated().anyRequest().hasAnyAuthority("ROLE_USER");
http.authorizeRequests().antMatchers("/admin/**").authenticated().anyRequest().hasAnyAuthority("ROLE_ADMIN");
http.httpBasic();
http.formLogin().permitAll();
}
}
And this is my controllers both user and admin.
#RestController
#RequestMapping("/admin")
public class AdminController {
#Autowired
private UserRepository userRepository;
#Autowired
private BCryptPasswordEncoder passwordEncoder;
#PostMapping("/addUser")
public String addUser(#RequestBody User user) {
user.setPassword(passwordEncoder.encode(user.getPassword()));
userRepository.save(user);
return "Added user by admin";
}
#GetMapping("/adminHello")
public String adminHello() {
return "Admin say hello";
}
}
#RestController
#RequestMapping("/user")
public class UserController {
#GetMapping("/userHello")
public String userHello() {
return "processing..";
}
}
If I try to login to http://localhost:8080/login and write in my password and my username. Then I will be able to go in. Fine!
But these are the problems.
If I enter http://localhost:8080/user/userHello with a Admin account, I still get "processing..."
If I enter http://localhost:8080/admin/adminHello with a Admin account, I get
"Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Fri Sep 13 00:23:42 CEST 2019
There was an unexpected error (type=Forbidden, status=403).
Forbidden"
Why? Have I forgot something? My Accound have the ADMIN role in the database. Very clear.
Clearly your Role's are not working there is some issue with configuration.
Try this
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/user/**").hasRole("USER")
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.httpBasic()
.and()
.formLogin().permitAll();
}

Configuring multiple authentication providers in conjunction with oauth2+jwt

I am trying to configure my Spring Boot 2, OAuth2 with JWT Authorization Server, which needs to do the following:
take a username/password, build a CustomUserDetails object based on db data and salesforce data, and return a JWT token if authentication passes (this works)
take a refresh token and return a new JWT refresh and access token (this works)
(NEW) take a refresh token, check against the db for a stored token ID before returning a new JWT access + refresh token (this is the trouble spot) The point of this is to ensure only one device is logged in with the user's credentials at a time.
In order to do #3, I am trying to customize the PreAuthenticatedAuthenticationProvider by giving it a custom UserDetailsService, and the AuthenticationManagerBuilder bean needs to be passed both the customized PreAuthenticatedAuthenticationProvider and the DaoAuthenticationProvider.
Assuming I am going in the right direction with that, here is my configuration code:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
UserDetailsService userDetailsService;
#Autowired
PasswordEncoder passwordEncoder;
//implements AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken>
#Autowired
CustomPreauthenticatedUserDetailsService customPreauthenticatedUserDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/actuator/**").permitAll()
.antMatchers("/swagger-ui**","/webjars/**","/swagger-resources/**", "/v2/**").permitAll()
.antMatchers("/oauth/token/revokeById/**").permitAll()
.antMatchers("/oauth/token/**").permitAll()
.anyRequest().authenticated()
.and().csrf().disable();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder authenticationManager) throws Exception {
authenticationManager.authenticationProvider(preauthAuthProvider());
authenticationManager.authenticationProvider(dbAuthProvider());
}
#Bean
#Qualifier(value = "authenticationManagerBean")
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean(value="preAuthProvider")
public PreAuthenticatedAuthenticationProvider preauthAuthProvider() {
PreAuthenticatedAuthenticationProvider preauthAuthProvider = new PreAuthenticatedAuthenticationProvider();
LOGGER.info("Setting customPreauthenticatedUserDetailsService");
preauthAuthProvider.setPreAuthenticatedUserDetailsService(customPreauthenticatedUserDetailsService);
return preauthAuthProvider;
}
#Bean(value="dbAuthProvider")
public DaoAuthenticationProvider dbAuthProvider() {
DaoAuthenticationProvider dbAuthProvider = new DaoAuthenticationProvider();
dbAuthProvider.setUserDetailsService(userDetailsService);
dbAuthProvider.setPasswordEncoder(passwordEncoder);
return dbAuthProvider;
}
}
On the AuthorizationServerConfig side:
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
private static final Integer ACCESS_TOKEN_VALIDITY_SECONDS = 300;
private static final Integer REFRESH_TOKEN_VALIDITY_SECONDS = 60 * 60 * 24 * 60;
#Autowired
public AuthorizationServerConfig(AuthenticationManager authenticationManagerBean, PasswordEncoder passwordEncoder, CustomTokenEnhancer customTokenEnhancer, TokenStore tokenStore, JwtAccessTokenConverter accessTokenConverter) {
this.authenticationManagerBean = authenticationManagerBean;
this.passwordEncoder = passwordEncoder;
this.customTokenEnhancer = customTokenEnhancer;
this.tokenStore = tokenStore;
this.accessTokenConverter = accessTokenConverter;
}
private AuthenticationManager authenticationManagerBean;
private PasswordEncoder passwordEncoder;
private CustomTokenEnhancer customTokenEnhancer;
private JwtAccessTokenConverter accessTokenConverter;
private TokenStore tokenStore;
#Override
public void configure(AuthorizationServerSecurityConfigurer authorizationServerSecurityConfigurer) {
authorizationServerSecurityConfigurer.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(ClientDetailsServiceConfigurer clientDetailsServiceConfigurer) throws Exception {
clientDetailsServiceConfigurer.inMemory().withClient("someclient")
.authorizedGrantTypes("password", "refresh_token")
.scopes("read", "write").accessTokenValiditySeconds(ACCESS_TOKEN_VALIDITY_SECONDS)
.refreshTokenValiditySeconds(REFRESH_TOKEN_VALIDITY_SECONDS)
.secret(this.passwordEncoder.encode("somepassword"));
}
#Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(customTokenEnhancer, accessTokenConverter));
endpoints.tokenStore(tokenStore).tokenEnhancer(tokenEnhancerChain)
.authenticationManager(this.authenticationManagerBean);
}
}
Upon start up of the app, the first sign of trouble I see is:
s.c.a.w.c.WebSecurityConfigurerAdapter$3 : No authenticationProviders and no parentAuthenticationManager defined. Returning null.
Oddly, that appears to be a lie, or it's talking about something else, because when I try to login with username and password - it works, and I get both my refresh and access tokens. The debugger shows that the ProviderManager has both my custom authentication provider classes and uses them for authentication.
However, when I try to retrieve a new token with the refresh token, the debugger shows that the app appears to be taking a different ProviderManager path - this ProviderManager only has PreAuthenticatedAuthenticationProvider in its list, and that provider is NOT the one I configured. This PreAuthenticatedAuthenticationProvider is trying to retrieve a UserDetailsService from WebSecurityConfigurerAdapter$UserDetailsServiceDelegator, and the result is an error:
2019-06-08 13:27:24.764 ERROR 8731 --- [nio-8080-exec-3] o.s.s.o.provider.endpoint.TokenEndpoint : Handling error: IllegalStateException, UserDetailsService is required.
So, what configuration step am I missing? Why does the refresh token call go elsewhere? Is it the authenticationManagerBean that I'm passing in the AuthorizationServerConfig?

How to renew access token with the refresh token in oauth2 in spring?

I am very new to spring and it is my first attempt at spring security with oauth2. I have implemented OAuth2 with spring security and I do get the access token and the refresh token. However, while sending the refresh token to get the new access token I got "o.s.s.o.provider.endpoint.TokenEndpoint - IllegalStateException, UserDetailsService is required."
The solution to similar problem by other users appeared to be attaching UserDetailsService with the endpoint.
So I did the same and now when I try to send the request to with grant_type: refresh_token and refresh_token: THE TOKEN along with the client id and secret, I get an error that the user was not found.
Please refer the WebSecurityConfiguration class below:
#EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter{
#Autowired
private UserDetailsService customUserDetailsService;
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean ();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailsService)
.passwordEncoder(encoder());
}
#Override
protected void configure (HttpSecurity http) throws Exception {
http.csrf().disable()
.antMatcher("/**")
.authorizeRequests()
.antMatchers("/login**")
.permitAll()
.anyRequest()
.authenticated();
}
public PasswordEncoder encoder() {
return NoOpPasswordEncoder.getInstance();
}
}
Please refer the AuthorizationServerConfiguration class below:
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private DataSource dataSource;
#Autowired
private CustomUserDetailsService userDetailsService;
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.tokenStore(tokenStore());
.userDetailsService(userDetailsService);
}
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
}
Please refer the ResourceServerConfiguration class below:
#Configuration
#EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter{
#Autowired
DataSource dataSource;
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId("scout").tokenStore(tokenStore());
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests (). antMatchers ("/oauth/token", "/oauth/authorize **").permitAll();
// .anyRequest (). authenticated ();
http.requestMatchers (). antMatchers ("/api/patients/**") // Deny access to "/ private"
.and (). authorizeRequests ()
.antMatchers ("/api/patients/**"). access ("hasRole ('PATIENT')")
.and (). requestMatchers (). antMatchers ("/api/doctors/**") // Deny access to "/ admin"
.and (). authorizeRequests ()
.antMatchers ("/api/doctors/**"). access ("hasRole ('DOCTOR')");
}
}
The CustomUserDetailsService class for reference if required:
#Service
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private UsersRepository userRepository;
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
Optional<Users> usersOptional = userRepository.findByEmail(email);
Users user = null;
if(usersOptional.isPresent()) {
System.out.println(usersOptional.isPresent());
user = usersOptional.get();
}else {
throw new RuntimeException("Email is not registered!");
}
return new CustomUserDetails(user);
}
}
As I think, the server should only check for the validity of the refresh token as we don't pass the user details with refresh token. So I don't know why it requires the userDetails in the first place.
Please help and guide if I am missing something!
Thanks in advance.
I don't sure. But as I see your code in WebSecurityConfiguration could wired default InMemoryUserDetailsManager UserDetailsService .That could be reason why you have 2 different provider. In one you write, from the other you read users. Please try change your code as I show below and let me know if it help:
Was:
#Autowired
private UserDetailsService customUserDetailsService;
My vision how should be:
#Autowired
private CustomUserDetailsService customUserDetailsService;

REST - Creating session

I write application which use Spring Security. Below I wrote my TokenFilter and WebSecurityConfig.
My question is how long SecurityContext does contain logged users?
Next question: is my security configuration session stateless or no? Is SecurityContext replacment for Session?
Does my application fulfill the stateless principle?
public class TokenFilter extends OncePerRequestFilter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private TokenHelper tokenHelper;
private String tokenHeader = "Auth";
#Override
protected void doFilterInternal(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
FilterChain filterChain) throws ServletException, IOException {
final String authToken = httpServletRequest.getHeader(this.tokenHeader);
final String username = tokenHelper.getUsernameFromToken(authToken);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
final UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (tokenHelper.validateToken(authToken, userDetails)) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities()
);
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
And Security Config:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UnauthorizedHandler unauthorizedHandler;
#Autowired
private UserDetailsService userDetailsService;
#Autowired
public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder
.userDetailsService(this.userDetailsService)
.passwordEncoder(this.passwordEncoder);
}
#Autowired
public BCryptPasswordEncoder passwordEncoder;
#Autowired
public TokenFilter tokenFilter;
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler)
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/login").permitAll()
.anyRequest().authenticated();
httpSecurity.addFilterBefore(
tokenFilter,
UsernamePasswordAuthenticationFilter.class
);
httpSecurity.headers().cacheControl();
}
}
SecurityContext is only used to access Authentication instance.
SecurityContext may be stored between your requests. This logic (storing and then restoring for new requests) is implemented in SecurityContextPersistenceFilter which, in turn, delegates this storage logic to SecurityContextRepository.
When you use SessionCreationPolicy.STATELESS, NullSecurityContextRepository is used (it is an implementation of SecurityContextRepository); it does NOT actually store SecurityContext anywhere, and it always 'restores' a fresh (empty) SecurityContext.
SessionCreationPolicy.STATELESS also forbids Spring Security to create http session in any of beans which form its machinery.
So when you use SessionCreationPolicy.STATELESS, the session is never created, and it is certainly not used to store SecurityContext. Each your request will have a fresh security context (with no Authentication in it), so each your request will perform a full-blown authentication.

Resources