Spring boot - google oauth2, store refresh token in database - java

i'm trying to get the refresh token from the user logged in my system, and store it in a database. So a different system in my ecosystem can access the stored refresh token, generate an access token with it and use the google calendar api with the user credentials.
So far i have managed to do the login with
#Configuration
public class AppConfig extends WebSecurityConfigurerAdapter {
#Autowired
private ClientRegistrationRepository clientRegistrationRepository;
#Override
public void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.authorizeRequests()
.antMatchers("/**").authenticated()
.anyRequest().permitAll()
.and()
.oauth2Login()
.authorizationEndpoint()
.authorizationRequestResolver(new CustomAuthorizationRequestResolver(
this.clientRegistrationRepository))
.and()
.and()
.rememberMe();
}
}
And
public class CustomAuthorizationRequestResolver implements OAuth2AuthorizationRequestResolver {
private final OAuth2AuthorizationRequestResolver defaultAuthorizationRequestResolver;
public CustomAuthorizationRequestResolver(
ClientRegistrationRepository clientRegistrationRepository) {
this.defaultAuthorizationRequestResolver =
new DefaultOAuth2AuthorizationRequestResolver(
clientRegistrationRepository, "/oauth2/authorization");
}
#Override
public OAuth2AuthorizationRequest resolve(HttpServletRequest request) {
OAuth2AuthorizationRequest authorizationRequest =
this.defaultAuthorizationRequestResolver.resolve(request);
return authorizationRequest != null ?
customAuthorizationRequest(authorizationRequest) :
null;
}
#Override
public OAuth2AuthorizationRequest resolve(
HttpServletRequest request, String clientRegistrationId) {
OAuth2AuthorizationRequest authorizationRequest =
this.defaultAuthorizationRequestResolver.resolve(
request, clientRegistrationId);
return authorizationRequest != null ?
customAuthorizationRequest(authorizationRequest) :
null;
}
private OAuth2AuthorizationRequest customAuthorizationRequest(
OAuth2AuthorizationRequest authorizationRequest) {
Map<String, Object> additionalParameters = new LinkedHashMap<>(authorizationRequest.getAdditionalParameters());
additionalParameters.put("access_type", "offline");
return OAuth2AuthorizationRequest.from(authorizationRequest)
.additionalParameters(additionalParameters)
.build();
}
}
how and where can i access the refresh token of the logged user?

I answered a similar question here, but it is in kotlin, so I'll add a java version for you.
These are two approaches to get the refresh token (or rather OAuth2AuthorizedClient, from which you can get the refresh token). Which one you use depend on your needs.
Inject and OAuth2AuthorizedClient representing the requesting user into an endpoint method:
#GetMapping("/foo")
void foo(#RegisteredOAuth2AuthorizedClient("google") OAuth2AuthorizedClient user) {
OAuth2RefreshToken refreshToken = user.getRefreshToken();
}
Outside the context of a request, you can inject OAuth2AuthorizedClientService into a managed component, and get the needed OAuth2AuthorizedClient instance with the client registration id and principal name:
#Autowired
private OAuth2AuthorizedClientService clientService;
public void foo() {
OAuth2AuthorizedClient user = clientService.loadAuthorizedClient("google", "principal-name");
OAuth2RefreshToken refreshToken = user.getRefreshToken();
}

Related

Can I generate a JWT for my Spring Security app without have a user?

I want to generate a JWT with expiration date for people to access the system without have to register and create a user. Is this posible? I have tried with JwtTokenProvider but it needs a LoginRequest to work also with Jwts.builder() also needs a user.
if you want to use spring security you can create security configration and extends WebSecurityConfigurerAdapter. Then important point is custom provider.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private CustomAuthenticationProvider customAuthenticationProvider;
#Autowired
private JWTConfigurer securityConfigurerAdapter;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
//you can write customAuth provider
auth.authenticationProvider(customAuthenticationProvider);
}
#Override
public void configure(WebSecurity web) throws Exception {
//Some ignore etc.
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.csrf()
.disable().and()
.headers()
.frameOptions()
.disable()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
//important here
.antMatchers("/api/v1/authentication/**").permitAll()
.anyRequest().authenticated()
.and()
.apply(securityConfigurerAdapter);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return this.authenticationManager();
}
}
This is Filter class which extends genericFilterBean. Every request is monitored in this class
You will check to it is right token
I create token TokenProvider class and depend into JWTFilter then use valideToken method.
if token is sended and not validate then throw exception
if token is not sended then go super method so the flow is continue and works auth.authenticationProvider. Spring knows to start customAuthenticationProvider behind the scene becouse of you set into SecurityConfiguration class
#Component
public class JWTFilter extends GenericFilterBean {
private final Logger log = LoggerFactory.getLogger(JWTFilter.class);
#Autowired
private TokenProvider tokenProvider;
#Autowired
private MessageSource msgSource;
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
try {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
//Resolve method is optional what you want to use
String jwt = resolveToken(httpServletRequest);
if (StringUtils.hasText(jwt)) {
//token validation is important becouse of expires date into token
// and you will check expired date
if (this.tokenProvider.validateToken(jwt)) {
String jwtMd5 = DigestUtils.md5Hex(jwt);
MDC.put("jwt",jwtMd5);
Authentication authentication = this.tokenProvider.getAuthentication(jwt);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
filterChain.doFilter(servletRequest, servletResponse);
}catch(Exception ex){
handleException((HttpServletResponse) servletResponse,ex);
}
}
private String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader(JWTConfigurer.AUTHENTICATION_HEADER);
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
String jwt = bearerToken.substring(7, bearerToken.length());
return jwt;
}
String jwt = request.getParameter(JWTConfigurer.AUTHENTICATION_TOKEN);
if (StringUtils.hasText(jwt)) {
return jwt;
}
return null;
}
}
You can use this class for create token or validate token
you define expire date for token expiration into create method.
#Component public class TokenProvider {
private final Logger log = LoggerFactory.getLogger(TokenProvider.class);
private static final String AUTHORITIES_KEY = "auth";
private static final String WTS_USER_ID = "wtsUserId";
private static final String CHANNEL_PERMISSIONS = "channelPermissions";
private static final String APP_ROLES = "appRoles";
private String secretKey;
private long tokenValidityInSeconds;
#Autowired private ApplicationProperties applicationProperties;
#PostConstruct public void init() {
this.tokenValidityInSeconds = 1000;
}
public String createToken(Authentication authentication, Boolean rememberMe) { List<String> authorities = authentication.getAuthorities().stream().map(authority -> authority.getAuthority())
.collect(Collectors.toList());
//Token creation format is this
// token will be three part important parts are claims and sign
// claims refers to body to use datas
// sign will use to validation
return Jwts.builder().setSubject(authentication.getName()).claim(AUTHORITIES_KEY, authorities)
.claim(WTS_USER_ID, ((JWTAuthentication) authentication).getWtsUserId())
.claim(CHANNEL_PERMISSIONS, ((JWTAuthentication) authentication).getChannelPermissions())
.claim(APP_ROLES, ((JWTAuthentication) authentication).getAppRoles())
.signWith(SignatureAlgorithm.HS512, secretKey).setExpiration(tokenValidityInSeconds).compact(); }
#SuppressWarnings("unchecked") public Authentication getAuthentication(String token) { Claims claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody();
List<String> list = (List<String>) claims.get(AUTHORITIES_KEY); Collection<? extends GrantedAuthority> authorities = list.stream()
.map(authority -> new SimpleGrantedAuthority(authority)).collect(Collectors.toList()); Integer wtsUserId = (Integer) claims.get(WTS_USER_ID); List<String> appRoles = (List<String>) claims.get(APP_ROLES);
ObjectMapper objectMapper = new ObjectMapper(); List<ChannelPermission> channelPermissions = objectMapper.convertValue(claims.get(CHANNEL_PERMISSIONS),
new TypeReference<List<ChannelPermission>>() {
});
return new JWTAuthentication(token, wtsUserId, claims.getSubject(), authorities, channelPermissions, appRoles); }
public boolean validateToken(String authToken) {
try {
Jwts.parser().setSigningKey(secretKey).parseClaimsJws(authToken);
return true;
} catch (SignatureException e) {
log.info("Invalid JWT signature: " + e.getMessage());
return false;
} } }
This is controller who anonymous people get a JWT token .You can give a new JWT token all request and this JWT has expires date becouse of you set a expiration date into provider class.
#RequestMapping(value = "/login", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public ApiResponse login(#RequestBody #Validated AuthenticationRequestDTO authenticationRequest) {
Authentication authentication = this.authenticationManager.authenticate(new JWTAuthentication(
RandomUid, RandomPwd, "anonymous"));
SecurityContextHolder.getContext().setAuthentication(authentication);
String token = tokenProvider.createToken(authentication, false);
return new ApiResponse(ApiResponseStatus.SUCCESS, new AuthenticationResponseDTO(token));
}

Spring Security authenticate user via post

I have a react app running on a separate port (localhost:3000) that i want to use to authenticate users with, currently i have a proxy setup to my Spring backend (localhost:8080).
Can I somehow manually authenticate instead of http.httpBasic() by sending a POST request to my backend and getting back a session cookie then include the cookie with every request? It would simplify the auth process on iOS side aswell (using this process i could only store the session cookie value in keychain and pass it with every request made to my api)
How would I disable csrf for non-browser requests?
Is there a better approach to this? Diffrent paths for browser and mobile auth?
{
"username": "user",
"password": "12345678"
}
Handle the request in spring controller
#PostMapping(path = "/web")
public String authenticateUser() {
//Receive the auth data here... and perform auth
//Send back session cookie
return "Success?";
}
My WebSecurityConfigurerAdapter.java
#Configuration
#EnableWebSecurity
public class WebsecurityConfig extends WebSecurityConfigurerAdapter {
private final DetailService detailService;
public WebsecurityConfig(DetailService detailService) {
this.detailService = detailService;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(detailService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic().disable()
.authorizeRequests()
.antMatchers(HttpMethod.POST,"/api/v1/authenticate/new").permitAll()
.antMatchers(HttpMethod.POST,"/api/v1/authenticate/web").permitAll()
.anyRequest().authenticated();
}
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedMethods("GET", "POST").allowedOrigins("http://localhost:8080");
}
};
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(14);
}
}
You can create an endpoint that takes user's credentials in a request body, perform authentication and then set tokens, and other required parameters in HttpOnly cookies.
After setting cookies, subsequent requests can read access/refresh token from cookies and add it in requests, you can then use custom CheckTokenEndpoint to verify tokens.
In the following example TokenParametersDto is a POJO that has username and password properties.
For issuing token (by verifying credentials) you can delegate call to TokenEndpoint#postAccessToken(....) or use its logic to your own method.
#PostMapping(path = "/oauth/http/token", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Void> issueToken(#RequestBody final #Valid #NotNull TokenParametersDto tokenParametersDto,
final HttpServletResponse response) {
final OAuth2AccessToken token = tokenService.issueToken(tokenParametersDto);
storeTokenInCookie(token, response);
return new ResponseEntity<>(HttpStatus.OK);
}
private void storeTokenInCookie(final OAuth2AccessToken token, final HttpServletResponse response) {
final Cookie accessToken = new Cookie("access_token", token.getValue());
accessToken.setHttpOnly(true);
accessToken.setSecure(sslEnabled);
accessToken.setPath("/");
accessToken.setMaxAge(cookieExpiration);
final Cookie tokenType = new Cookie("token_type", token.getTokenType());
tokenType.setHttpOnly(true);
tokenType.setSecure(sslEnabled);
tokenType.setPath("/");
tokenType.setMaxAge(cookieExpiration);
// Set Refresh Token and other required cookies.
response.addCookie(accessToken);
response.addCookie(tokenType);
}
Check this answer for disabling CSRF for a specific URL section.

IP Address verification in REST API with SpringSecurity

I have Spring Boot application with configured SpringSecurity. It uses token generated by UUID.randomUUID().toString(), returned by method login in UUIDAuthenticationService class in AuthUser object. Authorized users are kept in LoggedInUsers class. When I'm sending request to API token is verified by method findByToken in UUIDAuthenticationService class.
Lastly I added timeout for token verification. Now I want to add ip address verification. If user is logged in from address X.X.X.X (which is kept in AuthUser object) he should be authorized with his token only form address X.X.X.X. How to do it?
My SecurityConfig.java:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
#FieldDefaults(level = PRIVATE, makeFinal = true)
class SecurityConfig extends WebSecurityConfigurerAdapter {
private static final RequestMatcher PUBLIC_URLS = new OrRequestMatcher(
new AntPathRequestMatcher("/api/login/login"),
);
private static final RequestMatcher PROTECTED_URLS = new NegatedRequestMatcher(PUBLIC_URLS);
TokenAuthenticationProvider provider;
SecurityConfig(final TokenAuthenticationProvider provider) {
super();
this.provider = requireNonNull(provider);
}
#Override
protected void configure(final AuthenticationManagerBuilder auth) {
auth.authenticationProvider(provider);
}
#Override
public void configure(final WebSecurity web) {
web.ignoring()
.requestMatchers(PUBLIC_URLS);
web.httpFirewall(defaultHttpFirewall());
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(STATELESS)
.and()
.exceptionHandling()
// this entry point handles when you request a protected page and you are not yet
// authenticated
.defaultAuthenticationEntryPointFor(forbiddenEntryPoint(), PROTECTED_URLS)
.and()
.authenticationProvider(provider)
.addFilterBefore(restAuthenticationFilter(), AnonymousAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/api/admin/**").hasAuthority("ROLE_ADMIN")
.antMatchers("/api/application/**").hasAnyAuthority("ROLE_ADMIN", "ROLE_EMPLOYEE", "ROLE_PORTAL")
.antMatchers("/api/rezerwacja/**").hasAnyAuthority("ROLE_ADMIN", "ROLE_EMPLOYEE")
.anyRequest()
.authenticated()
.and()
.csrf().disable()
.formLogin().disable()
.httpBasic().disable()
.logout().disable();
}
#Bean
TokenAuthenticationFilter restAuthenticationFilter() throws Exception {
final TokenAuthenticationFilter filter = new TokenAuthenticationFilter(PROTECTED_URLS);
filter.setAuthenticationManager(authenticationManager());
filter.setAuthenticationSuccessHandler(successHandler());
return filter;
}
#Bean
SimpleUrlAuthenticationSuccessHandler successHandler() {
final SimpleUrlAuthenticationSuccessHandler successHandler = new SimpleUrlAuthenticationSuccessHandler();
successHandler.setRedirectStrategy(new NoRedirectStrategy());
return successHandler;
}
/**
* Disable Spring boot automatic filter registration.
*/
#Bean
FilterRegistrationBean disableAutoRegistration(final TokenAuthenticationFilter filter) {
final FilterRegistrationBean registration = new FilterRegistrationBean(filter);
registration.setEnabled(false);
return registration;
}
#Bean
AuthenticationEntryPoint forbiddenEntryPoint() {
return new HttpStatusEntryPoint(FORBIDDEN);
}
#Bean
public HttpFirewall defaultHttpFirewall() {
return new DefaultHttpFirewall();
}
}
AbstractAuthenticationProcessingFilter.java:
#FieldDefaults(level = PRIVATE, makeFinal = true)
public final class TokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private static final String BEARER = "Bearer";
public TokenAuthenticationFilter(final RequestMatcher requiresAuth) {
super(requiresAuth);
}
#Override
public Authentication attemptAuthentication(
final HttpServletRequest request,
final HttpServletResponse response) {
final String param = ofNullable(request.getHeader(AUTHORIZATION))
.orElse(request.getParameter("t"));
final String token = ofNullable(param)
.map(value -> removeStart(value, BEARER))
.map(String::trim)
.orElseThrow(() -> new BadCredentialsException("Missing Authentication Token"));
final Authentication auth = new UsernamePasswordAuthenticationToken(token, token);
return getAuthenticationManager().authenticate(auth);
}
#Override
protected void successfulAuthentication(
final HttpServletRequest request,
final HttpServletResponse response,
final FilterChain chain,
final Authentication authResult) throws IOException, ServletException {
super.successfulAuthentication(request, response, chain, authResult);
chain.doFilter(request, response);
}
}
TokenAuthenticationProvider/java:
#Component
#AllArgsConstructor(access = PACKAGE)
#FieldDefaults(level = PRIVATE, makeFinal = true)
public final class TokenAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
#NonNull
UserAuthenticationService auth;
#Override
protected void additionalAuthenticationChecks(final UserDetails d, final UsernamePasswordAuthenticationToken auth) {
// Nothing to do
}
#Override
protected UserDetails retrieveUser(final String username, final UsernamePasswordAuthenticationToken authentication) {
final Object token = authentication.getCredentials();
return Optional
.ofNullable(token)
.map(String::valueOf)
.flatMap(auth::findByToken)
.orElseThrow(() -> new UsernameNotFoundException("Cannot find user with authentication token=" + token));
}
}
UUIDAuthenticationService.java:
#Service
#AllArgsConstructor(access = PACKAGE)
#FieldDefaults(level = PRIVATE, makeFinal = true)
public final class UUIDAuthenticationService implements UserAuthenticationService {
private static final Logger log = LoggerFactory.getLogger(UUIDAuthenticationService.class);
#NonNull
UserCrudService users;
#Autowired
LoginManager loginMgr;
#Override
public AuthUser login(final String username, final String password) throws Exception { //throws Exception {
AuthUser user = loginMgr.loginUser(username, password);
if (user != null) {
users.delete(user);
users.save(user);
log.info("Zalogowano użytkownika {}, przydzielono token: {}", user.getUsername(), user.getUuid());
}
return Optional
.ofNullable(user)
.orElseThrow(() -> new RuntimeException("Błędny login lub hasło"));
}
#Override
public Optional<AuthUser> findByToken(final String token) {
AuthUser user = users.find(token).orElse(null); // get();
if (user != null) {
Date now = Date.from(OffsetDateTime.now(ZoneOffset.UTC).toInstant());
int ileSekund = Math.round((now.getTime() - user.getLastAccess().getTime()) / 1000); // timeout dla tokena
if (ileSekund > finals.tokenTimeout) {
log.info("Token {} dla użytkownika {} przekroczył timeout", user.getUuid(), user.getUsername());
users.delete(user);
user = null;
}
else {
user.ping();
}
}
return Optional.ofNullable(user); //users.find(token);
}
#Override
public void logout(final AuthUser user) {
users.delete(user);
}
}
I thought about creating method findByTokenAndIp in UUIDAuthenticationService, but I don't know how to find ip address of user sending request and how to get ip address while logging in login method in UUIDAuthenticationService (I need it while I'm creating AuthUser object).
You had access to HttpServletRequest request in your filter so you can extract the IP from it.
See https://www.mkyong.com/java/how-to-get-client-ip-address-in-java/
After having the IP, you can deny the request anyway that you want!
I would briefly do the following steps:
save the IP in the UUIDAuthenticationService. You can add HttpServletRequest request as a param, if you're using a controller/requestmapping, because it's auto-injected:
#RequestMapping("/login")
public void lgin(#RequestBody Credentials cred, HttpServletRequest request){
String ip = request.getRemoteAddr();
//...
}
Within the authentication filter, use the IP as the "username" for the UsernamePasswordAuthenticationToken and the token as the "password". There is also already the HttpServletRequest request that gives you the IP by getRemoteAddr().
It's also possible to create an own instance of AbstractAuthenticationToken or even UsernamePasswordAuthenticationToken, which explictly holds an IP or even the request for the authentication-manager.
Then, you just need to adapt the changes to your retrieveUser method.
I modified controller to get ip address with HttpServletRequest parameter and add parameter ipAddress to login method.
#PostMapping("/login")
public AuthUser login(InputStream inputStream, HttpServletRequest request) throws Exception {
final String ipAddress = request.getRemoteAddr();
if (ipAddress == null || ipAddress.equals("")) {
throw new Exception("Nie udało się ustalić adresu IP klienta");
}
Login login = loginMgr.prepareLogin(inputStream);
return authentication
.login(login.getUsername(), login.getPasword(), ipAddress);
}
And modified method retrieveUser in TokenAuthenticationProvider
#Override
protected UserDetails retrieveUser(final String username, final UsernamePasswordAuthenticationToken authentication) {
System.out.println("Verification: "+authentication.getPrincipal()+" => "+authentication.getCredentials());
final Object token = authentication.getCredentials();
final String ipAddress= Optional
.ofNullable(authentication.getPrincipal())
.map(String::valueOf)
.orElse("");
return Optional
.ofNullable(token)
.map(String::valueOf)
.flatMap(auth::findByToken)
.filter(user -> user.ipAddress.equals(ipAddress)) // weryfikacja adresu ip
.orElseThrow(() -> new UsernameNotFoundException("Cannot find user with authentication token=" + token));
}
And it works. Thans for help.

Spring boot security cannot log in after invalid credentials

I have problem with validating user credentials. When I give correct credentials first time everything goes OK but giving invalid credentials first and then give correct ones I get invalid credentials error. I use Postman Basic
Auth.
My config class:
#Configuration
#EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserService userService;
#Autowired
private CustomAuthenticationEntryPoint authenticationEntryPoint;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable().authorizeRequests()
.antMatchers(HttpMethod.POST ,"/login").permitAll()
.antMatchers("/admin").hasAuthority("ADMIN")
.anyRequest().authenticated().and().exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.ALWAYS).and()
.logout()
.deleteCookies("remove")
.invalidateHttpSession(true);
http.rememberMe().disable();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(this.userService)
.and().eraseCredentials(true);
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
And my controller class
#PostMapping
public ResponseEntity<?> loginButtonClicked(HttpServletRequest request) {
HttpSession session = request.getSession();
final String authorization = request.getHeader("Authorization");
String[] authorizationData=null;
if (authorization != null && authorization.startsWith("Basic")) {
// Authorization: Basic base64credentials
String base64Credentials = authorization.substring("Basic" .length()).trim();
String credentials = new String(Base64.getDecoder().decode(base64Credentials),
Charset.forName("UTF-8"));
// credentials = username:password
authorizationData = credentials.split(":", 2);
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(authorizationData[0], authorizationData[1],Arrays.asList(new SimpleGrantedAuthority("USER")));
User user = userService.findUserEntityByLogin(authorizationData[0]);
if(user != null && user.getFromWhenAcceptLoginAttempts() != null && (user.getFromWhenAcceptLoginAttempts()).isBefore(LocalDateTime.now())){
// Authenticate the user
Authentication authentication = authenticationManager.authenticate(authRequest);
SecurityContext securityContext = SecurityContextHolder.getContext();
securityContext.setAuthentication(authentication);
// Create a new session and add the security context.
session = request.getSession();
session.setAttribute("SPRING_SECURITY_CONTEXT", securityContext);
return new ResponseEntity<>(new LoginResponseObject(200,"ACCESS GRANTED. YOU HAVE BEEN AUTHENTICATED"), HttpStatus.OK);
}else{
session.getId();
SecurityContextHolder.clearContext();
if(session != null) {
session.invalidate();
}
return new ResponseEntity<>(new ErrorObject(403,"TOO MANY LOGIN REQUESTS","YOU HAVE ENTERED TOO MANY WRONG CREDENTIALS. YOUR ACCOUNT HAS BEEN BLOCKED FOR 15 MINUTES.", "/login"), HttpStatus.FORBIDDEN);
}
}else{
session.getId();
SecurityContextHolder.clearContext();
if(session != null) {
session.invalidate();
}
return new ResponseEntity<>(new ErrorObject(401,"INVALID DATA","YOU HAVE ENTERED WRONG USERNAME/PASSWORD CREDENTIALS", "/login"), HttpStatus.UNAUTHORIZED);
}
}
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public ObjectMapper objectMapper(){
return new ObjectMapper();
}
#Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
The problem is that the request is stored in cache due to your sessionCreationPolicy.
To avoid this problem, you could add .requestCache().requestCache(new NullRequestCache()) in your http security config to override the default request cache configuration, but be careful because this could create another side effect (it depends on your application).
In case you do not need the session, you can choose another session policy:
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).
Another alternative is to relay in Spring's BasicAuthenticationFilter. This filter does all the authentication logic for you. To enable it, you only have to add .httpBasic()in your http security configuration.
You may want to add a custom logic on authentication success/failure. In that case, you only have to create a custom filter (CustomBasicAuthenticationFilter) that extends BasicAuthenticationFilter class and overrides the methods onSuccessfulAuthentication()and onUnsuccessfulAuthentication(). You will not need to add .httpBasic() but you will need to insert your custom filter in the correct place:
.addFilterAfter(new CustomBasicAuthenticationFilter(authenticationManager), LogoutFilter.class).
Any of that 3 solutions will avoid your problem.
Try to write .deleteCookies("JSESSONID") in your SpringSecurityConfig class.

Spring Security doesn't recognize which logged in user

I add Spring Security to my Spring Boot application but after, authentication process server doesn't recognize the same user in next request. I use Angular 5 for UI and maybe this issue in UI side may be requested not include cookies. Help me, please understand why Spring Security doesn't recognize user with already logged in.
In web configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/rest/user/login").permitAll();
http.csrf().disable()
.authenticationProvider(authenticationProvider())
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandler)
.and()
.formLogin()
.permitAll()
.loginProcessingUrl("/rest/user/login")
.usernameParameter("username")
.passwordParameter("password")
.successHandler(logInSuccessHandler)
.failureHandler(authFailureHandler)
.and()
.logout().permitAll()
.logoutRequestMatcher(new AntPathRequestMatcher("/rest/user/logout", "POST"))
.logoutSuccessHandler(logoutHandler)
.and()
.sessionManagement()
.maximumSessions(1);
http.authorizeRequests().anyRequest().authenticated();
}
Session manager is activated .sessionManagement().maximumSessions(1);
And authentication complete successful my UserDetailsService implementation correctly return User object by login and password. But next request to this controller:
#GetMapping("/rest/list")
public RestList list() {
...
}
Redirect to :
#Component
public class UnauthorizedHandler implements AuthenticationEntryPoint {
#Override
public void commence(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
}
}
I have the logged in checker:
#Component
public class LoggedInChecker {
public User getLoggedInUser() {
User user = null;
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null) {
Object principal = authentication.getPrincipal();
// principal can be "anonymousUser" (String)
if (principal instanceof UserDetailsImpl) {
UserDetailsImpl userDetails = (UserDetailsImpl) principal;
user = userDetails.getUser();
}
}
return user;
}
}
I use this checker in UserService
#Service
public class UserServiceImpl implements UserService {
#Autowired
private UserRepository userRepository;
#Autowired
private LoggedInChecker loggedInChecker;
#Override
public User getUserByUsername(String username) {
return userRepository.findByUsername(username);
}
#Override
public List<String> getPermissions(String username) {
User user = userRepository.findByUsername(username);
return nonNull(user) ? user.getRoles().stream().map(Role::getRole).collect(toList()) : Lists.newArrayList();
}
#Override
public User getCurrentUser() {
return loggedInChecker.getLoggedInUser();
}
#Override
public Boolean isCurrentUserLoggedIn() {
// This place must call but nothing happening
return nonNull(loggedInChecker.getLoggedInUser());
}
}
I thought Spring automatically call my UserSevice for check authorization. But how to specify HttpSecurity for save information about the session?
In Angular side I use HttpClient:
#Injectable()
export class CoreApi {
private baseUrl = 'http://localhost:8080/rest/';
constructor(public http: HttpClient) {
}
public get(url: string = ''): Observable<any> {
return this.http.get(this.getUrl(url));
}
}
Maybe You know how to specify Spring Security for checking auth with LoggedInChecker or another way for this reslt, let me know. Thank You!

Categories