I'm using spring boot to develop an application, Here I want to show the "Name and Image" of the logged in user, So I use session to pass name and image after authentication. Its working if any user enter the user credentials (in login page) or if any logged in user directly type the URL for few minutes (www.abc.com/this/url). But after few minutes, session name and Image are not visible(session expired) but other function are working with that session. My code is
#Component
public class SecurityHandler implements AuthenticationSuccessHandler{
#Autowired
private UserService userService;
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
HttpSession session = request.getSession();
String userName = null;
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
userName = ((UserDetails) principal).getUsername();
} else {
userName = principal.toString();
}
User user = userService.findBySSO(userName);
session.setAttribute("userName", user.getFirstName());
session.setAttribute("imgPathh", user.getImagePath());
response.sendRedirect(request.getContextPath()+"/dashboard/index");
}
}
Common jsp page
<h2><c:out value="${userName }"></c:out></h2>
I want to know why this session variable doesn't work even after few minutes after the authentication ( anyhow if we type the URL directly, it should pass this authentication, am I correct?)
Update 1.
Security config
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("customUserDetailsService")
UserDetailsService userDetailsService;
#Autowired
PersistentTokenRepository tokenRepository;
#Autowired
SecurityHandler securityHandler;
#Autowired
HttpSession session;
#Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
auth.authenticationProvider(authenticationProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers() // antmachers
.and().formLogin().loginPage("/login").successHandler(securityHandler).loginProcessingUrl("/login").usernameParameter("ssoId").passwordParameter("password")
.and().rememberMe().rememberMeParameter("remember-me").tokenRepository(tokenRepository)
.tokenValiditySeconds(86400).and().csrf().and().exceptionHandling().accessDeniedPage("/Access_Denied")
.and()
.sessionManagement().sessionFixation().migrateSession()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED); //always, IF_REQUIRED,never ,stateless
http.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login")
.invalidateHttpSession(true)
.permitAll();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService);
authenticationProvider.setPasswordEncoder(passwordEncoder());
authenticationProvider.setHideUserNotFoundExceptions(false);
System.out.println("Error in DaoAuthenticationProvider");
return authenticationProvider;
}
#Bean
public PersistentTokenBasedRememberMeServices getPersistentTokenBasedRememberMeServices() {
PersistentTokenBasedRememberMeServices tokenBasedservice = new PersistentTokenBasedRememberMeServices(
"remember-me", userDetailsService, tokenRepository);
System.out.println("Error in PersistentTokenBasedRememberMeServices");
return tokenBasedservice;
}
#Bean
public AuthenticationTrustResolver getAuthenticationTrustResolver() {
System.out.println("Error in AuthenticationTrustResolver");
return new AuthenticationTrustResolverImpl();
}
}
It's called session timeout.
Once the session is timed out, or expired, that's it.
The user doesn't have any session in the server anymore.
The user will have to log-in again.
Try changing the session timeout if you want it to be retained longer.
Related
The objective is to establish an authentication mechanism as outlined below:
Retrieve the session token from the header within OncePerRequestFilter and create an Authentication object.
Develop a custom implementation of UserDetailsService.
In the loadByUsername method, initiate a REST call to retrieve the user object utilizing the session token.
Construct a UserDetails object that can be accessed subsequently inside the RestController class through the Principal object.
Custom Implementation of OncePerRequestFilter
#Component
#Slf4j
public class SessionTokenFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
log.info("Inside session filter");
String sessionToken = request.getHeader("session-token");
log.info("Session token " + sessionToken);
if (sessionToken != null) {
Authentication authentication = new UsernamePasswordAuthenticationToken(sessionToken, null);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
}
Custom implementation of UserDetailsService
#Service
#Slf4j
public class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
private RestTemplate restTemplate;
#Override
public UserDetails loadUserByUsername(String sessionToken) throws UsernameNotFoundException {
log.info("Inside UDS with session token " + sessionToken);
User user = null; //Make a rest call
if (sessionToken == null) {
throw new UsernameNotFoundException("Invalid session token");
}
user = new User();
user.setUsername("Harsh");
user.setPassword("Password");
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(),
new ArrayList<>());
}
}
Implementation of SecurityFilterChain as per Spring Boot 3.0
#Configuration
#EnableWebSecurity
public class SecurityConfiguration {
#Autowired
private UserDetailsService userDetailsService;
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((authz) -> authz.anyRequest().authenticated())
.addFilterBefore(new SessionTokenFilter(), UsernamePasswordAuthenticationFilter.class).userDetailsService(userDetailsService);
return http.build();
}
#Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}
Inside my RestController class
#GetMapping("/api/content/{id}")
public List<Feed> getDetails(#PathVariable(name = "id") long id, Principal principal) {
log.info("Principal " + principal);
//Business Logic
}
I am encountering an issue where, during each request, the SessionTokenFilter is being invoked but the custom UserDetailsService is not being invoked. Consequently, I am receiving a 403 error for each request. What could be the missing element that is causing this issue?
I am trying to secure my api requests with both basic authorization and jwt role based authorization.
I have two classes for basic auth and web security config. Both jwt role based auth and basic auth classed are imported in WebSecurityConfigurerAdapter.
When running the application, api is working only with basic auth and does not know jwt token included or not.
WebSecurityConfigurerAdapter class
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
final private static String REALM = "UWAPP_SECURITY_REALM";
#Autowired
UserDetailsServiceImpl userDetailsService;
#Autowired
private AuthEntryPointJwt unauthorizedHandler;
#Autowired
public WebSecurityConfig(UserDetailsServiceImpl userDetailsService) {
super();
this.userDetailsService = userDetailsService;
}
#Bean
public AuthTokenFilter authenticationJwtTokenFilter() {
return new AuthTokenFilter();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.authorizeRequests()
.antMatchers("/actuator/health", "/api/auth/signup", "/api/auth/login", "/api/auth/logout").permitAll()
.antMatchers("/api/test/public").permitAll()
.antMatchers("/api/test/user").hasAnyAuthority(UserLoginRole.USER.value())
.anyRequest().authenticated()
.and()
.formLogin().disable()
.csrf().disable()
.httpBasic().realmName(REALM)
.authenticationEntryPoint(getBasicAuthEntryPoint());
http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
http.cors();
}
#Bean
public BasicAuthentication getBasicAuthEntryPoint() {
return new BasicAuthentication();
}
}
BasicAuthentication class
public class BasicAuthentication extends BasicAuthenticationEntryPoint {
final private static String REALM = "UWAPP_SECURITY_REALM";
#Override
public void commence(final HttpServletRequest request, final HttpServletResponse response,
final AuthenticationException authException) throws IOException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.addHeader("WWW-Authenticate", "Basic realm=" + getRealmName() + "");
PrintWriter writer = response.getWriter();
writer.println("HTTP Status 401 : " + authException.getMessage());
}
#Override
public void afterPropertiesSet() {
setRealmName(REALM);
super.afterPropertiesSet();
}
}
Requests did not pass through authorization jwt token included or not.
What am I missing here?
I'm trying to setup a simple Oauth2 Server with Spring Boot with only client_credentials flow for now, using in memory users and clients etc. Basically the most basic project so I can build on it. Haven't used Oauth2 with Spring for some time so I'm a bit stuck. I can get access tokens but it seems Spring does not actually validate the username/password sent by the client. Here are the two config classes (AuthorizationServerConfigurerAdapter, WebSecurityConfigurerAdapter):
#Configuration
#EnableAuthorizationServer
public class OAuthConfiguration extends AuthorizationServerConfigurerAdapter {
private final AuthenticationManager authenticationManager;
private final PasswordEncoder passwordEncoder;
private final UserDetailsService userService;
#Value("${jwt.signing-key}")
private String jwtSigningKey;
#Value("${jwt.accessTokenValidititySeconds}")
private int accessTokenValiditySeconds;
#Value("${jwt.refreshTokenValiditySeconds}")
private int refreshTokenValiditySeconds;
public OAuthConfiguration(AuthenticationManager authenticationManager, PasswordEncoder passwordEncoder, UserDetailsService userService) {
this.authenticationManager = authenticationManager;
this.passwordEncoder = passwordEncoder;
this.userService = userService;
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("test-client-id")
.secret(passwordEncoder.encode("test-client-secret"))
.accessTokenValiditySeconds(accessTokenValiditySeconds)
.refreshTokenValiditySeconds(refreshTokenValiditySeconds)
.authorizedGrantTypes("client_credentials")
.scopes("read", "write")
.resourceIds("api");
}
#Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.accessTokenConverter(accessTokenConverter())
.userDetailsService(userService)
.authenticationManager(authenticationManager);
}
#Bean
JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(jwtSigningKey);
return converter;
}
}
and:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint;
public WebSecurityConfiguration(CustomAuthenticationEntryPoint customAuthenticationEntryPoint) {
this.customAuthenticationEntryPoint = customAuthenticationEntryPoint;
}
#Bean
public DaoAuthenticationProvider authenticationProvider(UserDetailsService userDetailsService) {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setPasswordEncoder(passwordEncoder());
provider.setUserDetailsService(userDetailsService);
return provider;
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
#Override
public UserDetailsService userDetailsServiceBean() throws Exception {
return super.userDetailsServiceBean();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user1")
.password("$2a$10$sWszOXuTlN0amQi8vXp4cerb.tJUQo.4FzLAnTCsSqChsYhlLdQWW")
.roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().disable().csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and().exceptionHandling().authenticationEntryPoint(customAuthenticationEntryPoint).accessDeniedHandler(new CustomAccessDeniedHandler());
}
}
So I can call the "/oauth/token" endpoint with any username/password I get an access token. However the client ID and client secret are being validated.
Any help is appreciated
It's the default behaviour with grant_type client_credentials. So, you don't need to pass the username and password to the token endpoint with this grant_type. In this flow, AuthorizationServer will only validate the client Id and client secret.
POST /token
Host: authorization-server.com
grant_type=client_credentials
&client_id=xxxxxxxxxx
&client_secret=xxxxxxxxxx
I have built a Java application with the REST API convention. I working on endpoint which returns objects only if object is connected with user by common id in database(ManyToOne annotation). In order to achieve that i need current logged user id for comapring it with object's user id. If Ids are the same, endpoint returns data. I know solutions as "Principal" or "Authentication" classes but they provide everything except of "id". I used spring security http basic for authentication.
My authentication classes:
#Component
public class CustomAuthenticator implements AuthenticationProvider {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
#Autowired
public CustomAuthenticator(UserRepository userRepository, #Lazy PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String login = authentication.getName();
String password = authentication.getCredentials().toString();
User user = userRepository.findByLogin(login).orElseThrow(() -> new EntityNotFoundException("User not found"));
if (!passwordEncoder.matches(password, user.getPassword())) {
throw new BadCredentialsException("Bad credentials");
}
return new UsernamePasswordAuthenticationToken(login, password, convertAuthorities(user.getRoles()));
}
private Set<GrantedAuthority> convertAuthorities(Set<UserRole> userRoles) {
Set<GrantedAuthority> authorities = new HashSet<>();
for (UserRole ur : userRoles) {
authorities.add(new SimpleGrantedAuthority(ur.getRole().toString()));
}
return authorities;
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
SECURITY CONFIG CLASS:
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableWebSecurity
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final CustomAuthenticator customAuthenticator;
public SecurityConfig(CustomAuthenticator customAuthenticator) {
this.customAuthenticator = customAuthenticator;
}
#Bean
public PasswordEncoder passwordEncoder() {
PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
return passwordEncoder;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/api").permitAll()
.antMatchers("/api/register").permitAll()
//TODO everybody now has access to database, change it later
.antMatchers("/h2-console/**").permitAll()
.anyRequest().authenticated()
.and()
.httpBasic();
http
.csrf().disable()
.headers().frameOptions().disable();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthenticator);
}
}
Does someone know how to resolve that problem ?
You can use UserDetails class and set id for the username field, this class provides by spring security.
If you don't want that solution, you can create a Subclass extend UserDetails class and decide an id field. When receiving the request, parse principal to UserDetails or subclass extends UserDetails to get the id
Ex:
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
UserDetails userPrincipal = (UserDetails)authentication.getPrincipal();
Here is the error details when i run.
APPLICATION FAILED TO START
Description:
Field userDetailsService in com.word.security.WebSecurityConfig
required a bean of type
'org.springframework.security.core.userdetails.UserDetailsService'
that could not be found.
Action:
Consider defining a bean of type
'org.springframework.security.core.userdetails.UserDetailsService' in
your configuration.
Here is the WebSecurityConfig.java class
#Configuration
#EnableWebSecurity
#Service
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
// disable caching
http.headers().cacheControl();
http.csrf().disable() // disable csrf for our requests.
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers(HttpMethod.GET, "/login").permitAll()
.antMatchers(HttpMethod.POST, "/createuser").permitAll()
.antMatchers(HttpMethod.GET, "/user/1").permitAll()
.anyRequest().authenticated()
.and()
// We filter the api/login requests
.addFilterBefore(new JWTLoginFilter("/login", authenticationManager()), UsernamePasswordAuthenticationFilter.class)
// And filter other requests to check the presence of JWT in header
.addFilterBefore(new JWTAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// Create a default account
auth.userDetailsService(userDetailsService());
}
#Bean
public UserDetailsService userDetailsService() {
return super.userDetailsService();
}
}
Intellj IDEA shows the Could not Autowire error for userDetailsService below;
#Autowired
private UserDetailsService userDetailsService;
However, on my another class named SecurityService ;
#Service
public class SecurityService {
#Autowired
IUserService userService;
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private UserDetailsService userDetailsService;
public User activeUser() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String username = auth.getName();
Optional<User> user = userService.getUserByName(username);
if (user.isPresent()) {
return user.get();
}
return null;
}
public void autologin(String username, String password) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, password, AuthorityUtils.createAuthorityList("USER"));
authenticationManager.authenticate(usernamePasswordAuthenticationToken);
if (usernamePasswordAuthenticationToken.isAuthenticated()) {
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
}
UPDATE
UserController.Class
#RestController
public class UserController {
#Autowired
private IUserService userService;
#Autowired
private SecurityService securityService;
#RequestMapping(value = "/createuser", method = RequestMethod.POST)
public String createUser(#RequestBody User user, Model md) {
if (userService.checkExistUserName(user.getUserName())) {
md.addAttribute("LoginError", true);
return "bu kullanici adi ile bir kullanici bulunmaktadir. Lutfen baska bir kullanici adi ile deneyiniz";
}
User newUser = new User();
newUser.setUserName(user.getUserName());
newUser.setFirstname(user.getFirstname());
newUser.setUserMail(user.getUserMail());
newUser.setSurname(user.getSurname());
newUser.setUserPassword(user.getUserPassword());
userService.saveUser(user);
/* Automatic login after register */
securityService.autologin(user.getUserName(), user.getUserPassword());
return user.getId().toString();
}
I'm not getting the same error as i have on WebSecurityConfig.java.
But now i m getting the StackoverFlow Error shown below after attempting to create an user;
java.lang.StackOverflowError: null at
org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$UserDetailsServiceDelegator.loadUserByUsername(WebSecurityConfigurerAdapter.java:441)
~[spring-security-config-4.2.1.RELEASE.jar:4.2.1.RELEASE] at
org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$UserDetailsServiceDelegator.loadUserByUsername(WebSecurityConfigurerAdapter.java:442)
~
its like going into the recursive loop. Dont know how to update.
WebSecurityConfigurerAdapter contains this method:
public UserDetailsService userDetailsServiceBean() throws Exception
From Java Docs:
Override this method to expose a UserDetailsService created from configure(AuthenticationManagerBuilder) as a bean.
So try to override this method in WebSecurityConfig like this:
#Bean
public UserDetailsService userDetailsService() {
return super.userDetailsService();
}
One of the reason for this error is as you do not have implementation of this class. Create a class called CustomUserDetailsService implementing UserDetailsService and annotate it with #Component.
Refer to spring documentation for more.