I am just beginner in Spring Security Oauth2.
I have Authorization Server and Resource Server (separated).
The flow that I used is Authorization Code. And I have success to login tho the Authorization Server, get the code, and then get the accessToken.
Then, I have a problem when I want to logout. When I deploy the Authorization Server in Apache Tomcat, it can call 'j_spring_security_logout' but when I deploy in Glashfish, it cant.
Here my security config:
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement().maximumSessions(1).maxSessionsPreventsLogin(false);
http.authorizeRequests().antMatchers(HttpMethod.OPTIONS, "/oauth/token").permitAll().and()
.authorizeRequests()
.antMatchers("/login.jsp").permitAll()
.and()
.formLogin()
.loginPage("/login.jsp")
.loginProcessingUrl("/j_spring_security_check")
.usernameParameter("j_username")
.passwordParameter("j_password")
.and()
.logout()
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
.logoutUrl("/j_spring_security_logout")
.logoutSuccessHandler(logoutHandler);
}
Here my logouthandler:
public void onLogoutSuccess(HttpServletRequest req, HttpServletResponse res, Authentication a) throws IOException, ServletException {
String token=req.getParameter("token");
if(token != null){
consumerTokenServices.revokeToken(token);
}
String redirect = req.getParameter("redirect");
if(redirect != null){
res.sendRedirect(redirect);
}
}
Related
Our team is developing a web application using Spring boot (2.2.2). It uses Spring security to handle the login process. We want the application to redirect back to the page before login (for eg, user access http://example.com/foo/bar -> if login session has expired then show login page -> if login succeeded then directs back to http://example.com/foo/bar)
Everything seems fine except that the application occasionally directs to the default page (for eg, http://example.com) instead of the page before login. When this happens, it seems that the page before login is not saved in the session (according to what my teammate reported). Is this due to our configuration problem?
Following is our WebSecurityConfig
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("...")
.anyRequest().authenticated()
.and()
.csrf().ignoringAntMatchers("...")
.and()
.formLogin()
.loginPage("/loginForm")
.loginProcessingUrl("/login")
.usernameParameter("userId")
.passwordParameter("password")
.failureUrl("/loginForm?error=true")
.permitAll()
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessHandler(logoutSuccessHandler())
.deleteCookies("JSESSIONID")
.invalidateHttpSession(true)
.permitAll()
.and()
.exceptionHandling()
.accessDeniedHandler(new CustomAccessDeniedHandler())
.and()
.sessionManagement()
.invalidSessionUrl("/loginForm")
;
}
Since successHandler is not set in WebSecurityConfig, SavedRequestAwareAuthenticationSuccessHandler would be called by default. Problem seems occurs in the following part in the onAuthenticationSuccess method:
#Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws ServletException, IOException {
SavedRequest savedRequest = requestCache.getRequest(request, response);
if (savedRequest == null) {
super.onAuthenticationSuccess(request, response, authentication);
return;
}
String targetUrlParameter = getTargetUrlParameter();
if (isAlwaysUseDefaultTargetUrl()
|| (targetUrlParameter != null && StringUtils.hasText(request
.getParameter(targetUrlParameter)))) {
requestCache.removeRequest(request, response);
super.onAuthenticationSuccess(request, response, authentication);
return;
}
clearAuthenticationAttributes(request);
// Use the DefaultSavedRequest URL
String targetUrl = savedRequest.getRedirectUrl();
logger.debug("Redirecting to DefaultSavedRequest Url: " + targetUrl);
getRedirectStrategy().sendRedirect(request, response, targetUrl);
}
Occasionally savedRequest is null so Spring security directs to the default page (http://example.com) after successful login. What would be the cause?
The redirection to context root is caused due to sessionManagement().invalidSessionUrl("/loginForm").
This is because you are making an explicit call to login page and hence it is treated as a requested page and not re-direction for authentication. Post authentication, spring would redirect to default url (which is context root by default unless overridden in config).
In my view this should happen always and not occasionally.
Pls remove the mentioned config and let spring handle the re-direction for authentication. :)
I have implemented these custom handler in my spring security app
AuthenticationSuccessHandler (SimpleUrlAuthenticationSuccessHandler)
AuthenticationFailureHandler (SimpleUrlAuthenticationFailureHandler)
AuthenticationEntryPoint
so I can get JSON response as per my requirement.
In case of successful login I am getting valid JSON response as it is going through my custom AuthenticationSuccessHandler but in case of invalid credential it is giving me a HTTP Status 401 - Authentication Failed: Bad credentials along with it's default HTML error page.
I want a JSON error response for this instead of default HTML page.
Is there any other handler which I need to implement? If it there then how to configure it in config method?
Here is my Spring Security config method:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/list").access("hasRole('USER') or hasRole('ADMIN') or hasRole('DBA')")
.antMatchers("/delete-user-*").access("hasRole('ADMIN')")
.antMatchers("/edit-user-*").access("hasRole('ADMIN') or hasRole('DBA')")
.and()
.httpBasic()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.formLogin()
.loginProcessingUrl("/login")
.usernameParameter("ssoId")
.passwordParameter("password")
.successHandler(authenticationSuccessHandler)
.failureHandler(authenticationFailureHandler)
.and()
.rememberMe()
.rememberMeParameter("remember-me")
.tokenRepository(tokenRepository)
.tokenValiditySeconds(86400)
.and()
.csrf()
.disable()
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler);
}
Here is my CustomAuthenticationFailureHandler
#Component
public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
response.setStatus(HttpStatus.BAD_REQUEST.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("UTF-8");
JSONObject jsonResponse = new JSONObject();
jsonResponse.put("error", true);
jsonResponse.put("message", "Invalid credentials");
response.getWriter().append(jsonResponse.toString());
super.onAuthenticationFailure(request, response, exception);
}
}
I have tried multiple things but nothing is working for me till now.
I have a problem with Spring Security authentication failure handler redirect with parameter.
In security config when I use
failureUrl("/login.html?error=true")
it works. But when I use custom authentication failure handler (as shown below), it always returns: url/login.html
getRedirectStrategy().sendRedirect(request, response, "/login.html?error=true");
or
response.sendRedirect(request.getContextPath() + "/login.html?error=true");
I don't know whats wrong. Why does it not show the parameter ?error=true?
Info: I am using Spring + JSF + Hibernate + Spring Security
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.html")
.usernameParameter("j_username")
.passwordParameter("j_password")
.loginProcessingUrl("/j_spring_security_check")
.failureHandler(customAuthenticationFailureHandler)// .failureUrl("/login.html?error=true")//.successHandler(authSuccsessHandler)
.defaultSuccessUrl("/dashboard.html")
.permitAll()
.and()
.logout()
.invalidateHttpSession(true)
.logoutSuccessUrl("/")
.permitAll()
.and()
.exceptionHandling()
.accessDeniedPage("/access.html")
.and()
.headers()
.defaultsDisabled()
.frameOptions()
.sameOrigin()
.cacheControl();
http
.csrf().disable();
}
This is custom authentication failure handler:
#Component
public class CustomAuthFailureHandler extends SimpleUrlAuthenticationFailureHandler {
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
getRedirectStrategy().sendRedirect(request, response, "/login.html?error=true");
}
}
I will change parameter for some cases.
You didn't allow anonymous access to URL /login.html?error=true, so you are redirected to the login page (/login.html).
AbstractAuthenticationFilterConfigurer#permitAll allows access (for anyone) to failure URL but not for custom failure handler:
Ensures the urls for failureUrl(String) as well as for the HttpSecurityBuilder, the getLoginPage() and getLoginProcessingUrl() are granted access to any user.
You have to allow access explicitly with AbstractRequestMatcherRegistry#antMatchers:
Maps a List of AntPathRequestMatcher instances that do not care which HttpMethod is used.
and ExpressionUrlAuthorizationConfigurer.AuthorizedUrl#permitAll:
Specify that URLs are allowed by anyone.
You don't have to allow the exact URL /login.html?error=true, because AntPathRequestMatcher ignores the query string:
Matcher which compares a pre-defined ant-style pattern against the URL ( servletPath + pathInfo) of an HttpServletRequest. The query string of the URL is ignored and matching is case-insensitive or case-sensitive depending on the arguments passed into the constructor.
Your modified configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/login.html").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.html")
.usernameParameter("j_username")
.passwordParameter("j_password")
.loginProcessingUrl("/j_spring_security_check")
.failureHandler(customAuthenticationFailureHandler)// .failureUrl("/login.html?error=true")//.successHandler(authSuccsessHandler)
.defaultSuccessUrl("/dashboard.html")
.permitAll()
.and()
.logout()
.invalidateHttpSession(true)
.logoutSuccessUrl("/")
.permitAll()
.and()
.exceptionHandling()
.accessDeniedPage("/access.html")
.and()
.headers()
.defaultsDisabled()
.frameOptions()
.sameOrigin()
.cacheControl();
http
.csrf().disable();
}
In the case of OAuth token failure, I am getting below response, which is inconsistent with app response style.
{
"error": "invalid_token",
"error_description": "Invalid access token: 4cbc6f1c-4d47-44bd-89bc-92a8c86d88dbsdfsdfs"
}
I just wanted to use common response object for the consistency.
Following approach worked for me.
Build your resource server with your custom entry-point object
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.authenticationEntryPoint(new CustomOAuth2AuthenticationEntryPoint());
}
and here is your custom entry point
public class CustomOAuth2AuthenticationEntryPoint extends OAuth2AuthenticationEntryPoint{
public CustomOAuth2AuthenticationEntryPoint() {
super.setExceptionTranslator(new CustomOAuth2WebResponseExceptionTranslator());
}
}
here is your custom WebResponseExceptionTranslator, In my case I have just used a replica of DefaultWebResponseExceptionTranslator and rewritten handleOAuth2Exception method.
CustomOAuth2WebResponseExceptionTranslator implements WebResponseExceptionTranslator<Response> {
....
.....
private ResponseEntity<Response> handleOAuth2Exception(OAuth2Exception e) throws IOException {
int status = e.getHttpErrorCode();
HttpHeaders headers = new HttpHeaders();
headers.set("Cache-Control", "no-store");
headers.set("Pragma", "no-cache");
if (status == HttpStatus.UNAUTHORIZED.value() || (e instanceof InsufficientScopeException)) {
headers.set("WWW-Authenticate", String.format("%s %s", OAuth2AccessToken.BEARER_TYPE, e.getSummary()));
}
ResponseEntity<Response> response =new ResponseEntity<>(new Response().message(e.getMessage()).status(StatusEnum.ERROR)
.errorType(e.getClass().getName()), HttpStatus.UNAUTHORIZED);
return response;
}
Result looks like
{
"status": "error",
"message": "Invalid access token: 4cbc6f1c-4d47-44bd-89bc-92a8c86d88dbsdfsdfs",
"error_type": "org.springframework.security.oauth2.common.exceptions.InvalidTokenException"
}
I have created a following LogoutSuccessHandlerImpl
public class LogoutSuccessHandlerImpl extends SimpleUrlLogoutSuccessHandler {
private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
#Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
final Long currentUserRoleId = SecurityUtils.getCurrentUserRoleId();
request.getSession().invalidate();
SecurityContextHolder.clearContext();
request.setAttribute("isLoggedOut", "true");
if(currentUserRoleId == UserRole.ADMIN.getId()){
redirectStrategy.sendRedirect(request, response, Constants.ADMIN_LOGIN_URL);
} else {
redirectStrategy.sendRedirect(request, response, Constants.APPLICANT_LOGIN_URL);
}
}
}
and below is my security config.
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling()
.accessDeniedPage("/accessDenied")
.and()
.authorizeRequests()
.accessDecisionManager(accessDecisionManager)
.antMatchers("/").permitAll()
.antMatchers("/application/register**").permitAll()
.antMatchers("/adminLogin**").permitAll()
.antMatchers("/error**").permitAll()
.antMatchers("/checkLogin**").permitAll()
.anyRequest().fullyAuthenticated()
.and()
.formLogin()
.loginPage("/")
.loginProcessingUrl("/checkLogin")
.defaultSuccessUrl("/dashboard")
.failureHandler(new LoginFailureHandlerImpl())
.usernameParameter("username")
.passwordParameter("password")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessHandler(new LogoutSuccessHandlerImpl())
.deleteCookies("JSESSIONID")
.permitAll()
.and()
.headers()
.frameOptions()
.disable()
.and()
.sessionManagement()
.maximumSessions(1);
}
Below is my SecurityUtils.
public static SecurityUser getCurrentUser() {
SecurityContext securityContext = SecurityContextHolder.getContext();
Authentication authentication = securityContext.getAuthentication();
if (authentication != null) {
if (authentication.getPrincipal() instanceof SecurityUser) {
return (SecurityUser) authentication.getPrincipal();
}
}
throw new IllegalStateException("User not found!");
}
public static Long getCurrentUserRoleId() {
return SecurityUtils.getCurrentUser().getRoleId();
}
and the error i get is
java.lang.IllegalStateException: User not found!
at com.portal.core.user.security.SecurityUtils.getCurrentUser(SecurityUtils.java:34)
at com.portal.core.user.security.SecurityUtils.getCurrentUserRoleId(SecurityUtils.java:38)
at com.portal.core.user.security.LogoutSuccessHandlerImpl.onLogoutSuccess(LogoutSuccessHandlerImpl.java:22)
authentication is always null, because LogoutSuccessHandler is called after a successful logout, see LogoutSuccessHandler:
Strategy that is called after a successful logout by the LogoutFilter, to handle redirection or forwarding to the appropriate destination.
You could implement a custom LogoutHandler to get user's role id before a successful logout, see LogoutHandler#logout:
Parameters:
request - the HTTP request
response - the HTTP response
authentication - the current principal details
LogoutSuccessHandler will be called after all the LogoutHandlers are finished executing. This means by the Your code in LogoutSuccessHandlerImpl.onLogoutSuccess is called when there will be nothing in SecurityContextHolder. This is because a SecurityContextLogoutHandler will be registered by default. The following line in your LogoutSuccessHandlerImpl has no effect as it is already done by SecurityContextLogoutHandler.
SecurityContextHolder.clearContext();
You can do the following.
Store the User Id and Role Id in the Http Session
Configure your logout to have logout().invalidateHttpSession(false)
Modify your LogoutSuccessHandlerImpl.onLogoutSuccess to get the User Id and Role Id from HttpSession instead of SecurityContextHolder
For multiple purposes I need to use
SecurityContextHolder.getContext().getAuthentication()
methods in my controllers/services.
I did migrate my app to Spring Boot 1.4.1 from XML configured Spring MVC app (now only Java configs), similar approach worked before.
I have a problem calling SecurityContextHolder.getContext().getAuthentication(), for example in this controller:
#RestController
#Secured("IS_AUTHENTICATED_ANONYMOUSLY")
#RequestMapping("utils")
public class UtilsController {
#RequestMapping(value = "/check_auth", method = RequestMethod.GET)
public Boolean getAuthState() throws SQLException {
if (SecurityContextHolder.getContext().getAuthentication() == null){
logger.info("Auth obj null");
}
if (SecurityContextHolder.getContext().getAuthentication().getName() != null && SecurityContextHolder.getContext().getAuthentication().getName() != "anonymousUser") {
return true;
} else return false;
}
}
it always returns null. Can't figure out why anonymous authentication is not working.
Here is the Spring Security configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).and()
.formLogin()
.successHandler(ajaxSuccessHandler)
.failureHandler(ajaxFailureHandler)
.loginProcessingUrl("/authentication")
.passwordParameter("password")
.usernameParameter("username")
.and()
.logout()
.deleteCookies("JSESSIONID")
.invalidateHttpSession(true)
.logoutUrl("/logout")
.logoutSuccessUrl("/")
.and()
.csrf().disable()
// .anonymous().disable()
.authorizeRequests()
// .anyRequest().anonymous()
.antMatchers("/utils").permitAll()
.antMatchers("/oauth/token").permitAll()
.antMatchers("/admin/*").access("hasRole('ROLE_ADMIN')")
.antMatchers("/user/*").access("hasRole('ROLE_USER')");
}
I did tried with and without #Secured annotation on the controller.
.authorizeRequests()
// .anyRequest().anonymous()
.antMatchers("/utils").permitAll()
different variations with this settings.
You are getting null with:
SecurityContextHolder.getContext().getAuthentication()
because you are not authenticating within you security configuration.
You can add a simple:
.authenticated()
.and()
// ...
.formLogin();
in case you're using form login.
Now after you'll authenticate each request you suppose to get something other than null.
Here's an example from Spring Security docs:
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/resources/**", "/signup", "/about").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")
.anyRequest().authenticated()
.and()
// ...
.formLogin();
}