Spring Security: Redirect to Login Page in case of 401 - spring

I have an application that exposes a REST API and is secured using Spring Security. Is there a way to automatically redirect the client (from the server side) to the login page if a request sent to my server results in 401 - unauthorised?

For spring-security application based on spring-boot.
Define a handler bean:
#Component
public class CommenceEntryPoint implements AuthenticationEntryPoint, Serializable {
private static final long serialVersionUID = 565662170056829238L;
// invoked when user tries to access a secured REST resource without supplying any credentials,
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
// send a json object, with http code 401,
// response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
// redirect to login page, for non-ajax request,
response.sendRedirect("/login.html");
}
}
In security config class (e.g WebSecurityConfig):
Autowire the bean:
#Autowired
private CommenceEntryPoint unauthorizedHandler; // handle unauthorized request,
Specify handler:
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() // excepion handler,
Tips:
This is suitable only for non-ajax request,
For ajax request, better return 401 code, and let the frontend handle it.
If you want to use 401 response, in CommenceEntryPoint.commence() just use response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); instead of response.sendRedirect().

You can specify how your application handles exception or HTTP status codes by specifying it in error-page element of web.xml
eg: web.xml
<error-page>
<error-code>401</error-code>
<location>/login.html</location>
</error-page>
Same way you handle other HTTP status code viz 404 for page not found page.

I solved this issue by adding the following elements to my Spring Security XML under an http node:
<security:access-denied-handler error-page="/#/login" />
<security:session-management invalid-session-url="/#/login" session-fixation-protection="changeSessionId" />

Related

Default 401 instead of redirecting for OAuth2 login Spring Security

I support two types of authentication and need to return 401 for most paths instead of redirects. For Keycloak I used the HttpUnauthorizedEntryPoint below and its fine, but for the OAuth2 login, it prevents the automatic redirect (on "/auth/challenge" in my case) to "/oauth2/authorization/azure" on NegatedRequestMatcher(/login, and some other things) to be put in place. The valid process is reflected in logs below:
org.springframework.security.web.util.matcher.NegatedRequestMatcher: matches = true
org.springframework.security.web.authentication.DelegatingAuthenticationEntryPoint: Match found! Executing org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint#112c824c
org.springframework.security.web.context.SecurityContextPersistenceFilter: SecurityContextHolder now cleared, as request processing completed org.springframework.security.web.DefaultRedirectStrategy: Redirecting to 'http://localhost:2222/oauth2/authorization/azure'
This is the code that adds the Ouath2 bit to the common configuration:
#Throws(Exception::class)
override fun configure(http: HttpSecurity) {
http.commonConfiguration()
.exceptionHandling()
.authenticationEntryPoint(HttpUnauthorizedEntryPoint())
.and()
.oauth2Login()
.userInfoEndpoint()
.oidcUserService(userService)
}
public class HttpUnauthorizedEntryPoint implements AuthenticationEntryPoint {
private static final Logger LOG = LoggerFactory.getLogger(HttpUnauthorizedEntryPoint.class);
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException arg) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "This endpoint requires authorization.");
}
}
The question is, how can I by default return 401 and let all the OAuth2 redirects to be placed under the hood?
Thanks in advance.
I know that because of the HttpUnauthorizedEntryPoint the DelegatingAuthenticationEntryPoint is not filled in by Spring Security. I tried to add this manually, but I would rather have this process done by Spring.
val entryPoints = LinkedHashMap<RequestMatcher, AuthenticationEntryPoint>()
entryPoints[loginPageMatcher] = LoginUrlAuthenticationEntryPoint("/oauth2/authorization/azure")
val loginEntryPoint = DelegatingAuthenticationEntryPoint(entryPoints)
loginEntryPoint.setDefaultEntryPoint(HttpUnauthorizedEntryPoint())
My guess is you serve both a REST API and server-side rendered UI (Thymeleaf, JSF or whatever) and you want to return
401 for unauthorized requests to #RestController
302 (redirect to login) for unauthorized requests to UI pages.
Define two SecurityFilterChain beans:
first with client configuration restricted to UI paths
#Order(Ordered.HIGHEST_PRECEDENCE)
http.securityMatcher(new OrRequestMatcher(new AntPathRequestMatcher("/login/**"), new AntPathRequestMatcher("/oauth2/**"), ...); // add here all paths to UI resources
http.oauth2Login(); // and all client configuration you need for UI
http.authorizeHttpRequests().requestMatchers("/login/**").permitAll().requestMatchers("/oauth2/**").permitAll().anyRequest().authenticated();
second being default with resource-server configuration (no securityMatcher and an order greater than HIGHEST_PRECEDENCE)
Details in this answer and that tutorial

How to return http status code instead of login page in multi HttpSecurity case

I have a spring boot app which provides HTML page service via / and also rest api via /api. The former requires login via a Login form and the later requires HTTP basic auth, and hereby, I configure two HttpSecurity section as follows:
#Configuration
#Order(1)
public static class ApiSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/**")
.cors().and().csrf().disable()
.authorizeRequests().anyRequest().authenticated()
.and()
.httpBasic(Customizer.withDefaults());
}
}
#Configuration
public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**")
.cors().disable()
.authorizeRequests()
.antMatchers("/login", "/js/**", "/css/**").permitAll()
.antMatchers("/**").hasRole("ADMIN")
.and().formLogin().loginPage("/login").permitAll().defaultSuccessUrl("/index")
.and().logout().invalidateHttpSession(true)
.deleteCookies("JSESSIONID").logoutSuccessUrl("/login").permitAll();
}
}
The configuration works perfectly in normal case. However, if wrong credentials are provides in rest api clients, an HTML code of login page with HTTP status code 200 returns from the app instead of an excepted json message with HTTP status code 401 or 403.
I'm afraid it is because the url pattern of /api/** both matches "/api/**" and "/**", and therefore, the request will pass both the filter chain for rest api and HTML page. Finally, because of the lower order of formLogin, a login page returns in the case.
So, How can I get the excepted result for rest api clients? Is there an only solution to separate the two url patterns which should not match each other?
Addition 1:
I think there are three cases which will raise exceptions in the security filter chain:
No credentials provided;
Wrong credentials provided;
Right credentials provided but not matched the required roles
And the results for the cases are as follows:
Return HTTP status 401 with a json error message
Return HTTP status 302 and try to redirect to login page
Return HTTP status 403 with a json error message
Therefore, it seems that only the case of wrong credentials provided will
be routed to /error endpoint (as what Eleftheria said in the answer), and the difference between 1,3 and 2 is the exception type -- org.springframework.security.access.AccessDeniedException: Access is denied for 1 and 3; org.springframework.security.authentication.BadCredentialsException: Bad credentials for 2.
In the formLogin() case, if the BadCredentialsException rises, it will be routed to the failure-url, but how to configure the failure-url in the httpbasic case? (seems no such method in HttpBasicConfigurer)
This is happening because the failed authentication is throwing an exception and the "/error" page is secured by your second filter chain.
Try permitting all requests to "/error" in your second filter chain.
http.antMatcher("/**")
.authorizeRequests()
.antMatchers("/error").permitAll()
// ....
Note that the request will only be processed by one filter chain, and that is the first filter chain that it matches.
The request to "/api/123" is only processed by the first filter chain, however the second filter chain was invoked because there was an error.

Spring boot Whitelabel page error for unauthorized ldap group access

I'm developing a web application in spring boot 2.0.0 and spring security 5.0.6 and I've had perfect luck with it until I added some logic that validates that the user is part of an LDAP group. If the user provides invalid credentials, the login page is shown again with a validation error displayed, but when the credentials are correct but the user is not part of the required LDAP group, the application is redirected to a Whitelabel Error Page that, along with that title in bold letters shows this:
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Fri Feb 22 18:40:55 EST 2019
There was an unexpected error (type=Forbidden, status=403).
Forbidden
This is the correct error, so I know the authentication is working. But I want to stay on the login page and not redirect, and I cannot figure out how to do that.
My entire configure method for my WebSecurityConfigurerAdapter is shown here below:
#Override
protected void configure(HttpSecurity http) throws Exception {
// Disabling CSRF because it causes issues with API requests (POSTs don't
// contain the CSRF tokens).
http.csrf().disable();
http.headers().frameOptions().disable();
http.authorizeRequests()
// This line turns off authentication for all management endpoints, which means all
// endpoints that start with "/actuator" (the default starter path for management endpoints
// in spring boot applications). To selectively choose which endpoints to exclude from authentication,
// use the EndpointRequest.to(String ... method, as in the following example:
// .requestMatchers(EndpointRequest.to("beans", "info", "health", "jolokia")).permitAll()
.requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll()
// Do not authenticate resource requests
.antMatchers(
"/app/css/**",
"/app/img/**",
"/app/js/**",
"/app/bootstrap/**").permitAll()
.antMatchers(
"/admin/**",
"/app/builds/**",
"/app/monitor/**",
"/app/review/**")
.hasRole(requiredRole)
// All other requests are authenticated
.anyRequest().authenticated()
// Any unauthenticated request is forwarded to the login page
.and()
.formLogin()
.loginPage(LOGIN_FORM)
.permitAll()
.successHandler(successHandler())
.and()
.exceptionHandling()
.authenticationEntryPoint(delegatingAuthenticationEntryPoint())
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher(LOGOUT_FORM))
.logoutSuccessUrl(LOGIN_FORM);
}
I'm open to critique of the construct of this entire method, btw--I've taken over this project and this is new to me. This code was working perfectly before I introduced the 6 lines ending with .hasRole(requiredRole), and it still works as long as the user is part of the required group.
I haven't provided the source for some of the methods that are called here, and I'm happy to paste them if someone would like. I'm guessing someone that knows this stuff well will spot the problem right away.
Any advice would be appreciated.
The solution I landed upon is similar to those I've found by googling around. I created an error controller that resolves to /error. At first I didn't think my error handler was ever being reached because I wasn't properly configured in my editor to debug a spring boot application, but I resolved that this morning. I added an error controller that looks like this:
#Controller
public class MyErrorController implements ErrorController {
private static final String ERROR_PATH = "/error";
#Autowired
private ErrorAttributes errorAttributes;
#RequestMapping(value = ERROR_PATH)
public String error(WebRequest request, HttpServletRequest httpServletRequest) {
Map<String, Object> attrs = this.errorAttributes.getErrorAttributes(request, false);
Integer status = (Integer) attrs.get("status");
if (status != null && status == 403) {
httpServletRequest.setAttribute("unauthorized", true);
}
return "/app/login";
}
#Override
public String getErrorPath() {
return ERROR_PATH;
}
}
So whenever an unauthorized attempt is made, this controller is hit and the condition of a status 403 triggers the logical branch in this method. I added the request attribute "unauthorized" and forward to the login jsp page, to which I've added a section that detects the presence of the request attribute. Voila, I'm all set.

Error 403 after #EnableOAuth2Sso in Spring security

I've got my own mapping setCred/ and when it gets called via a http POST request it returns a 403 Error. But when I remove the #EnableOAuth2Sso it all works fine.
I don't have any idea what part I'm missing here.
#EnableOAuth2Sso
#Controller
public class TestAPI {
#RequestMapping(value = "/setCred", method = RequestMethod.POST, consumes = "application/json", produces = "text/plain")
public ResponseEntity<?> setCred(#RequestBody StringToken json) throws JsonParseException, JsonMappingException, IOException {
ResponseEntity<?> res = null;
....
My end goal is to add a facebook login on my webpage (from https://spring.io/guides/tutorials/spring-boot-oauth2/)
I've got also security.basic.enabled=false in my application.properties
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
}
Starting with Spring Security 4.0 CSRF protection is enabled by default, I removed it for now, at the time my front end is not handling any CSRF tokens. But in the future I'll make sure to include a CSRF token within each POST request. Of course depending on one's intentions CSRF may be or not enabled, the only "bad" part about enabling the CSRF is adding more complexity to our code.

Authentication and authorization in REST Services with Liferay

We are building some services that will be exposed through a RESTful API. Primary customers of this API are Liferay portlets using Angular JS, meaning there are direct calls from client-side (Angular) to our services.
So far we have designed an authentication and authorization mechanism to assure that we can identify which logged user (Liferay) is requesting our API.
PS.: note that although we are using Liferay, it could be any other Java based application instead.
What we have designed is:
When the user logs in in our portal, Liferay creates an authentication token with userLogin (or ID) + client IP + timestamp. This token is saved in a cookie;
Before every REST call, Angular reads this cookie and sends its contents via a HTTP header;
Our service "decrypts" the cookie content sent and verifies if the timestamp is valid, the IP is the same and, according to our business rules, if the user has access to do or read whatever he wants to.
This design looks consistent to us right now and, depending on the algorithm we choose to create this token, we believe it is a secure approach.
Our doubts are:
Are we, somehow, reinventing the wheel not using HTTP authentication with some kind of custom provider? How to?
Could Spring Security help us with that? We have read some articles about it but it's not clear if it makes sense to use it with a non-Spring application;
Are there any security flaws we have not considered with this approach?
Thank you in advance. Any help is appreciated.
Filipe
Spring security solves the problem description, and as a bonus you will get all the spring security features for free.
The Token approach is great and here is how you can secure your APIs with spring-security
Implements AuthenticationEntryPoint and have the commence method set 401 instead of re-direction 3XX as follows
httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED,"Access Denied");
Have a TokenProcessingFilter extend and leverage what UsernamePasswordAuthenticationFilter has to offer, override the doFilter() method, extract the the token from the request headers, validate and Authenticate the token as follows
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = this.getAsHttpRequest(request);
String authToken = this.extractAuthTokenFromRequest(httpRequest);
String userName = TokenUtils.getUserNameFromToken(authToken);
if (userName != null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(userName);
if (TokenUtils.validateToken(authToken, userDetails)) {
UsernamePasswordAuthenticationToken authentication =new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
chain.doFilter(request, response);
}
Your Spring-security configuration will look like
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AuthFailure authFailure;
#Autowired
private AuthSuccess authSuccess;
#Autowired
private EntryPointUnauthorizedHandler unauthorizedHandler;
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private AuthenticationTokenProcessingFilter authTokenProcessingFilter;
#Autowired
public void configureAuthBuilder(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // Restful hence stateless
.and()
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandler) // Notice the entry point
.and()
.addFilter(authTokenProcessingFilter) // Notice the filter
.authorizeRequests()
.antMatchers("/resources/**", "/api/authenticate").permitAll()
//.antMatchers("/admin/**").hasRole("ADMIN")
//.antMatchers("/providers/**").hasRole("ADMIN")
.antMatchers("/persons").authenticated();
}
}
-- Last you will need another end point for Authentication and token-generation
Here is a spring MVC example
#Controller
#RequestMapping(value="/api")
public class TokenGenerator{
#Autowired
#Lazy
private AuthenticationManager authenticationManager;
#Autowired
private UtilityBean utilityBean;
#Autowired
private UserDetailsService userDetailsService;
#RequestMapping(value="/authenticate", method=RequestMethod.POST, consumes=MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<?> generateToken(#RequestBody EmefanaUser user){
ResponseEntity<?> response = null;
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserId(),user.getCredential());
try {
Authentication authentication = authenticationManager.authenticate(authenticationToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
/*
* Reload user as password of authentication principal will be null
* after authorization and password is needed for token generation
*/
UserDetails userDetails = userDetailsService.loadUserByUsername(user.getUserId());
String token = TokenUtils.createToken(userDetails);
response = ResponseEntity.ok(new TokenResource(utilityBean.encodePropertyValue(token)));
} catch (AuthenticationException e) {
response = ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
return response;
}
}
1 Generate token, 2. subsequent API-calls should have the token
Yes spring-security can do this and you don`t have to break new grounds in Authentication, Authorization.
Hope this helps
I'm late to the party but here are my two cents.
Disclaimer: The previous answers are a possible way to tackle this.
The next insight is what I've learned while implementing RESTful APIs
in Liferay.
If I understand correctly the question then you have two scenarios here. The first one is you need to create a RESTful api that will be called by already Logged in users. This means that the AJAX calls will, probably, get execute within the client's renderization of the portal. The main issue here is the security, how to secure yous REST calls.
First of all I think one should try to leverage on whatever framework one is using before implementing something else. Liferay DOES uses Spring in the backend but they've already implemented security. I would recommend to use the Delegate Servlet.
This servlet will execute any custom class and put it inside Liferay's Authentication path, meaning that you could just use PortalUtil.getUser(request) and if it's 0 or null then the user is not authenticated.
In order to use the delegate servlet you just need to configure it in your web.xml file
<servlet>
<servlet-name>My Servlet</servlet-name>
<servlet-class>com.liferay.portal.kernel.servlet.PortalDelegateServlet</servlet-class>
<init-param>
<param-name>servlet-class</param-name>
<param-value>com.samples.MyClass</param-value>
</init-param>
<init-param>
<param-name>sub-context</param-name>
<param-value>api</param-value>
</init-param>
<load-on-startup>3</load-on-startup>
</servlet>
As you can see we are instantiating another servlet. This servlet is going to be defined by the PortalDelegateServlet. The Delegate Servlet will use whatever class is on the value of the sevlet-class param. Within that class you can just check if there's a valid username in the HttpServletRequest object with Liferay's Utils and if there is then the user is OK to go.
Now, the way you access this is that the Delegate Servlet uses the value of the sub-context to know which class are you refering to from the URL. So, in this example you'll be access com.samples.MyClass by going to https://my.portal/delegate/api The 'delegate' part will always be there, the second part of the URL is what we define in the init-param. Notice that you can only define one level of the URI for sub-context, i.e. you can't set /api/v2.0/ as sub-context.
From then on you can do whatever you want on your servlet class and handle the parsing of the REST URI as you want.
You can also use spring's Dispatcher class as the class that the Delegate Servlet will call and just setup a spring servlet, hence having url annotation mappins.
It is important to know that this is only good for RESTful or Resource
serving, since the Delegate Servlet will not know how to handle
renderization of views.
The second scenario you have is to be able to call this RESTful API from any external application (doesn't matter what implementation they have). This is an entire different beast and I would have to reference the answer by iamiddy and using Spring's Authentication Token could be a nice way to do this.
Another way to do this, would be to handle unauthorized users in your servlet class by sending them to the login page or something of the sort. Once they succesfully login Liferay's Utils should recognize the authenticated user with the request. If you want to do this within an external application then you would need to mock a form-based login and just use the same cookie jar for the entire time. Although I haven't tried this, in theory it should work. Then again, in theory, communism works.
Hope this help some other poor soul out there.
Take a look at Single Sign On and Spring Security OAuth2 token authentication.
Here is example: sso-with-oauth2-angular-js-and-spring-security.
Note that Spring 4.2 might have some handy CORS support.
I can't uprate someone's answer with my current rating but The answer above is probably the right direction.
It sounds like what you need to investigate is something named CORS which provides security with cross site scripting. I'm sorry I don't quite know how it works yet (I'm in the same situation) but this is the main topic of this NSA document on REST
For Spring, try here to start maybe?

Resources