How to disable spring security for particular url - java

I am using stateless spring security,but in case of signup i want to disable spring security.I disabled using
antMatchers("/api/v1/signup").permitAll().
but it is not working,i am getting error below:
message=An Authentication object was not found in the SecurityContext, type=org.springframework.security.authentication.AuthenticationCredentialsNotFoundException
I think this means spring security filters are working
My url's order always will be "/api/v1"
My spring config is
#Override
protected void configure(HttpSecurity http) throws Exception {
http.
csrf().disable().
sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).
and().
authorizeRequests().
antMatchers("/api/v1/signup").permitAll().
anyRequest().authenticated().
and().
anonymous().disable();
http.addFilterBefore(new AuthenticationFilter(authenticationManager()), BasicAuthenticationFilter.class);
}
My authentication filter is
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = asHttp(request);
HttpServletResponse httpResponse = asHttp(response);
String username = httpRequest.getHeader("X-Auth-Username");
String password = httpRequest.getHeader("X-Auth-Password");
String token = httpRequest.getHeader("X-Auth-Token");
String resourcePath = new UrlPathHelper().getPathWithinApplication(httpRequest);
try {
if (postToAuthenticate(httpRequest, resourcePath)) {
processUsernamePasswordAuthentication(httpResponse, username, password);
return;
}
if(token != null){
processTokenAuthentication(token);
}
chain.doFilter(request, response);
} catch (InternalAuthenticationServiceException internalAuthenticationServiceException) {
SecurityContextHolder.clearContext();
logger.error("Internal authentication service exception", internalAuthenticationServiceException);
httpResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
} catch (AuthenticationException authenticationException) {
SecurityContextHolder.clearContext();
httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, authenticationException.getMessage());
} finally {
}
}
private HttpServletRequest asHttp(ServletRequest request) {
return (HttpServletRequest) request;
}
private HttpServletResponse asHttp(ServletResponse response) {
return (HttpServletResponse) response;
}
private boolean postToAuthenticate(HttpServletRequest httpRequest, String resourcePath) {
return Constant.AUTHENTICATE_URL.equalsIgnoreCase(resourcePath) && httpRequest.getMethod().equals("POST");
}
private void processUsernamePasswordAuthentication(HttpServletResponse httpResponse,String username, String password) throws IOException {
Authentication resultOfAuthentication = tryToAuthenticateWithUsernameAndPassword(username, password);
SecurityContextHolder.getContext().setAuthentication(resultOfAuthentication);
httpResponse.setStatus(HttpServletResponse.SC_OK);
httpResponse.addHeader("Content-Type", "application/json");
httpResponse.addHeader("X-Auth-Token", resultOfAuthentication.getDetails().toString());
}
private Authentication tryToAuthenticateWithUsernameAndPassword(String username,String password) {
UsernamePasswordAuthenticationToken requestAuthentication = new UsernamePasswordAuthenticationToken(username, password);
return tryToAuthenticate(requestAuthentication);
}
private void processTokenAuthentication(String token) {
Authentication resultOfAuthentication = tryToAuthenticateWithToken(token);
SecurityContextHolder.getContext().setAuthentication(resultOfAuthentication);
}
private Authentication tryToAuthenticateWithToken(String token) {
PreAuthenticatedAuthenticationToken requestAuthentication = new PreAuthenticatedAuthenticationToken(token, null);
return tryToAuthenticate(requestAuthentication);
}
private Authentication tryToAuthenticate(Authentication requestAuthentication) {
Authentication responseAuthentication = authenticationManager.authenticate(requestAuthentication);
if (responseAuthentication == null || !responseAuthentication.isAuthenticated()) {
throw new InternalAuthenticationServiceException("Unable to authenticate Domain User for provided credentials");
}
logger.debug("User successfully authenticated");
return responseAuthentication;
}
My controller is
#RestController
public class UserController {
#Autowired
UserService userService;
/**
* to pass user info to service
*/
#RequestMapping(value = "api/v1/signup",method = RequestMethod.POST)
public String saveUser(#RequestBody User user) {
userService.saveUser(user);
return "User registerted successfully";
}
}
I am totally new to spring,please help me how to do it ?

When using permitAll it means every authenticated user, however you disabled anonymous access so that won't work.
What you want is to ignore certain URLs for this override the configure method that takes WebSecurity object and ignore the pattern.
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/api/v1/signup");
}
And remove that line from the HttpSecurity part. This will tell Spring Security to ignore this URL and don't apply any filters to them.

I have a better way:
http
.authorizeRequests()
.antMatchers("/api/v1/signup/**").permitAll()
.anyRequest().authenticated()

<http pattern="/resources/**" security="none"/>
Or with Java configuration:
web.ignoring().antMatchers("/resources/**");
Instead of the old:
<intercept-url pattern="/resources/**" filters="none"/>
for exp . disable security for a login page :
<intercept-url pattern="/login*" filters="none" />

This may be not the full answer to your question, however if you are looking for way to disable csrf protection you can do:
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/web/admin/**").hasAnyRole(ADMIN.toString(), GUEST.toString())
.anyRequest().permitAll()
.and()
.formLogin().loginPage("/web/login").permitAll()
.and()
.csrf().ignoringAntMatchers("/contact-email")
.and()
.logout().logoutUrl("/web/logout").logoutSuccessUrl("/web/").permitAll();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin").password("admin").roles(ADMIN.toString())
.and()
.withUser("guest").password("guest").roles(GUEST.toString());
}
}
I have included full configuration but the key line is:
.csrf().ignoringAntMatchers("/contact-email")

As #M.Deinum already wrote the answer.
I tried with api /api/v1/signup. it will bypass the filter/custom filter but an additional request invoked by the browser for /favicon.ico, so, I add this also in web.ignoring() and it works for me.
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/api/v1/signup", "/favicon.ico");
}
Maybe this is not required for the above question.

If you want to ignore multiple API endpoints you can use as follow:
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable().authorizeRequests()
.antMatchers("/api/v1/**").authenticated()
.antMatchers("api/v1/authenticate**").permitAll()
.antMatchers("**").permitAll()
.and().exceptionHandling().and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}

I faced the same problem here's the solution:(Explained)
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(HttpMethod.POST,"/form").hasRole("ADMIN") // Specific api method request based on role.
.antMatchers("/home","/basic").permitAll() // permited urls to guest users(without login).
.anyRequest().authenticated()
.and()
.formLogin() // not specified form page to use default login page of spring security.
.permitAll()
.and()
.logout().deleteCookies("JSESSIONID") // delete memory of browser after logout.
.and()
.rememberMe().key("uniqueAndSecret"); // remember me check box enabled.
http.csrf().disable(); **// ADD THIS CODE TO DISABLE CSRF IN PROJECT.**
}

Related

AuthenticationProvider not authenticating

I am making custom token authentication in java spring boot, but it doesn't work. Please help.
This is my SecurityConfigurerAdapter :
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled=true)
public class MyWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Autowired
private BokiAuthenticationProvider bokiAuthenticationProvider;
#Autowired
private MyCredentialsFilter myCredentialsFilter;
#Override
protected void configure(HttpSecurity http) throws Exception {
// request handling
http.authorizeRequests()
.antMatchers(HttpMethod.GET, "/users").hasRole("USER")
.antMatchers(HttpMethod.GET, "/users/*").hasRole("USER")
.antMatchers(HttpMethod.POST, "/users").permitAll()
.antMatchers(HttpMethod.PATCH, "/users/*").hasRole("USER")
.antMatchers(HttpMethod.DELETE, "/users/*").hasRole("USER")
.antMatchers(HttpMethod.POST, "/login").permitAll()
;
// disable csrf
http.csrf().disable();
// app session is stateless
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(myCredentialsFilter, UsernamePasswordAuthenticationFilter.class);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.eraseCredentials(false)
.authenticationProvider(bokiAuthenticationProvider);
}
}
This is my filter. The request comes into the filter first. The token string is in the request header. I make a UsernamePasswordAuthenticationToken object out of it :
#Component
public class CredentialsFilter extends OncePerRequestFilter{
#Autowired
private MyCriptoService myCriptoService;
public CredentialsFilter(){
super();
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
if(request.getRequestURI().contains("login")){
chain.doFilter(request, response);
}else{
String token = request.getHeader("MyTokenHeader");
String username = myCriptoService.getUsernameFromToken(token);
if (username!=null && SecurityContextHolder.getContext().getAuthentication()==null){
UsernamePasswordAuthenticationToken
authentication = new UsernamePasswordAuthenticationToken(
username,
myCriptoService.getPasswordFromToken(token),
myCriptoService.getAuthoritiesFromToken(token));
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response);
}
}
}
}
And this is my AuthenticationProvider :
#Component
public class BokiAuthenticationProvider implements AuthenticationProvider {
#Autowired
private MyUserRepository myUserRepository;
#Autowired
private MyCriptoService myCryptoService;
#Override
public Authentication authenticate(Authentication auth) throws AuthenticationException {
String username = auth.getName();
if(username!=null && !"".equals(username)){
MyUserJPA jpa = myUserRepository.findByUsername(username);
if(jpa!=null){
String password = auth.getCredentials().toString();
if(myCryptoService.checkPasswords(password, jpa.getPassword())){
#SuppressWarnings("unchecked")
List<SimpleGrantedAuthority> authorities = (List<SimpleGrantedAuthority>) auth.getAuthorities();
return new UsernamePasswordAuthenticationToken(
jpa.getUsername(),
null,
authorities);
}
throw new MyBadCredentialsException("Passwords is missing or invalid.");
}
throw new MyBadCredentialsException("There is no user with username = "+username);
}
throw new MyBadCredentialsException("You did not provide a username.");
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
I did debugging. The filter fires and does the .doFilter(request,response), but the AuthenticationProvider doesn't even start.
What am i doing wrong ?
It turns out that authentication provider was authenticating, but there was a problem with the database. I recreated the database, and now it works.
Also, it is impossible for debugging to enter the authenticate-method in the authentication provider once the program is running. That was why my debug was failing.
The source of my confusion was also that my fiddler was not displaying me the JSON from the GET request, but that was an issue with the Fiddler which i solved.
Now I have tested it in more detail now, and everything is working.

Redirect loop when x509 authentication fails

I have spring boot application with x509 authentication. My problem is that when authentication fails i get redirect loop instead of error screen.
When authentication fails i throw UsernameNotFoundException loadUserDetails from method in ArhivX509UserDetailsService.java
My code is as follows:
SecurityConfiguration.java
#Configuration
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("x509UserDetailsService")
private AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> iX509UserDetailsService;
#Override
protected void configure(HttpSecurity pHttp) throws Exception {
//#formatter:off
pHttp
.authorizeRequests()
.antMatchers("/webjars/**").permitAll()
.antMatchers("/error").permitAll()
.antMatchers("/error401").permitAll()
.anyRequest().authenticated()
.and()
.x509()
.subjectPrincipalRegex("(.*)")
.authenticationUserDetailsService(iX509UserDetailsService)
.and()
.addFilterAfter(new X509ErrorCheckerFilter(), X509AuthenticationFilter.class)
.addFilterBefore(new LoggerMDCFilter(), X509ErrorCheckerFilter.class)
.exceptionHandling()
.authenticationEntryPoint(unauthorizedEntryPoint())
.accessDeniedPage(AppUrls.ERROR_401)
.and()
.requiresChannel()
.anyRequest()
.requiresSecure()
.and()
.sessionManagement()
.maximumSessions(1)
.and()
.and()
.logout()
.invalidateHttpSession(true)
.deleteCookies("SESSION", "JSESSIONID")
.logoutSuccessUrl("http://www.google.com")
.permitAll();
//#formatter:on
}
#Bean
public AuthenticationEntryPoint unauthorizedEntryPoint() {
return new AuthenticationEntryPoint() {
#Override
public void commence(HttpServletRequest pRequest, HttpServletResponse pResponse,
AuthenticationException pAuthException) throws IOException, ServletException {
pResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
};
}
}
ArhivX509UserDetailsService.java
#Service("x509UserDetailsService")
public class ArhivX509UserDetailsService
implements AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> {
#Autowired
private IUserProfileService iUserProfileService;
#Autowired
private ICheckCertificateService iCheckCertService;
#Override
public UserDetails loadUserDetails(PreAuthenticatedAuthenticationToken pToken) throws UsernameNotFoundException {
X509Certificate tCertificate = (X509Certificate) pToken.getCredentials();
String tSubjectDN = tCertificate.getSubjectDN().toString().toUpperCase();
ProfilKorisnika tProfilKorisnika = iUserProfileService.getUserProfile(tSubjectDN);
if (tProfilKorisnika == null) {
throw new UsernameNotFoundException("Pogreška kod prijave korisnika.");
}
return tProfilKorisnika;
}
}
X509ErrorCheckerFilter.java
public class X509ErrorCheckerFilter extends GenericFilterBean {
private static final String DEFAULT_REDIRECT_URL = "/error401";
private static final String[] UNAUTHENTICATED_URLS = { "/webjars/**", "/error", "/error401", "/login",
"/logout" };
#Override
public void doFilter(ServletRequest pRequest, ServletResponse pResponse, FilterChain pChain)
throws IOException, ServletException {
HttpServletRequest tHttpRequest = (HttpServletRequest) pRequest;
HttpServletResponse tHttpResponse = (HttpServletResponse) pResponse;
String tContextRoot = tHttpRequest.getSession().getServletContext().getContextPath();
String tUri = tHttpRequest.getRequestURI().replaceFirst(tContextRoot, "");
if (isUriSecured(tUri)) {
Authentication tAuthentication = SecurityContextHolder.getContext().getAuthentication();
AuthenticationException tException = (AuthenticationException) tHttpRequest
.getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
if (tException != null || tAuthentication == null) {
RequestDispatcher tRd = tHttpRequest.getRequestDispatcher(DEFAULT_REDIRECT_URL);
tRd.forward(tHttpRequest, tHttpResponse);
return;
}
}
pChain.doFilter(pRequest, pResponse);
}
private boolean isUriSecured(String pRequestURI) {
boolean tResult = true;
for (String tUrl : UNAUTHENTICATED_URLS) {
if (pRequestURI.startsWith(tUrl)) {
tResult = false;
break;
}
}
return tResult;
}
}
If you need more details please ask.
Applying permitAll() to /error401 means that when the security filterchain is finished executing, the request will be processed normally, regardless of whether or not there is an Authentication in the current SecurityContext.
X509ErrorCheckerFilter is forwarding all unauthenticated requests to /error401 from within the filterchain, so permitAll() is never applied. Instead, the forwarded request goes through the filterchain again and fails authentication, causing the circular redirect.
To resolve this, you have several options. Here are a couple:
1. Disable Security for error endpoints
you can disable security for endpoints using web.ignoring() in your SecurityConfiguration class. This is the simplest option.
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/error*");
}
2. Implement the ErrorController interface
The security filterchain will not be called for request mappings included in a controller that implements ErrorController.
See Spring's own BasicErrorController for reference(source).
This second option is preferable as it removes any requirement for a redirect in your filter. Instead, it lets Spring Security do the heavy lifting for routing requests through the auth process. As long as there's no authenticated Authentication in the session's SecurityContext when the security filterchain has finished processing, spring security will return a 401 and return the error page specified by your ErrorController.

Spring Security bypass URL or Filter

I have a Spring Boot application that is only exposing a REST API. I need to secure it and I'm using a token-based approach ― specifically JWT.
So far, this is what I have implemented:
//
// The Spring Security configuration class
#EnableGlobalAuthentication
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/api/login", "/api/logout").permitAll()
.antMatchers("/api/**").authenticated()
.anyRequest().authenticated()
.and()
.addFilterBefore(new JwtFilter(), BasicAuthenticationFilter.class)
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
//
// The JWT filter class to check for the token in the HTTP request (headers)
public final class JwtFilter extends GenericFilterBean {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
#Override
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws
IOException, ServletException {
final HttpServletRequest req = (HttpServletRequest)request;
final String header = req.getHeader("Authorization");
logger.debug("{} {}", req.getMethod(), req.getRequestURI());
if ((null == header) || !header.startsWith("Bearer ")) {
logger.debug("Missing or invalid Authorization header");
}
try {
// Check the token here; the implementation is not relevant here
/*SecurityContextHolder.getContext()
.setAuthentication(manager.authenticate(new JwtToken(JWTParser.parse(header.substring(7)))));*/
chain.doFilter(request, response);
} catch (final AuthenticationException e) {
SecurityContextHolder.clearContext();
// Do some other stuff here
} catch (final ParseException e) { /* ... */ }
}
}
The issue is that the filter executes correctly for every single URI, but I want to be able to exclude some endpoints from the same set. My API is placed in this context /api/* and I want to exclude, for instance, /api/login and /api/logout.
NOTE: My Spring Boot application.yml file doesn't have settings to enable/modify any security-related features.
Filters will be executed for all the endpoints that are configured through HttpSecurity. If you do not want filters to be applied for certain endpoints, include them in a method that configures WebSecurity. For example,
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/api/login", "/api/logout");
}
Please read this post for more details.
I am doing the same as mentioned in the solution
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors()
.and().csrf().ignoringAntMatchers("/api/auth/**").disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler)
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests()
.antMatchers("api/content/**").permitAll()
.anyRequest().authenticated();
http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("api/auth/signup");
}
Every signup request is still hitting the doFilterInternal() method which was is a custom method.
While running EnableWebSecurity in debug mode i get :
Security filter chain:
[WebAsyncManagerIntegrationFilter
SecurityContextPersistenceFilter
HeaderWriterFilter
CorsFilter
LogoutFilter
AuthTokenFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
AnonymousAuthenticationFilter
SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor ]
What should i do to solve this ?

Spring Security 3.2.1 Multiple login forms with distinct WebSecurityConfigurerAdapters

I'm using Spring Security 3.2.1.RELEASE with Spring MVC 4.0.4.RELEASE
I'm trying to setup Spring Security for a web application that will have two distinct login entry pages. I need the pages to be distinct as they will be styled and accessed differently.
First login page is for Admin users and protects admin pages /admin/**
Second login page is for Customer users and protects customer pages /customer/**.
I've attempted to setup two subclasses of WebSecurityConfigurerAdapter configuring individual HttpSecurity objects.
CustomerFormLoginWebSecurity is protecting customer pages and redirecting to customer login page if not authorised.
The AdminFormLoginWebSecurity is protecting admin pages redirecting to admin login page if not authorised.
Unfortunately it seems that only the first of the configurations is enforced. I think that I am missing something extra to make these both work.
#Configuration
#EnableWebSecurity
public class SecurityConfig {
#Autowired
public void registerGlobalAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("customer").password("password").roles("CUSTOMER").and()
.withUser("admin").password("password").roles("ADMIN");
}
#Configuration
#Order(1)
public static class CustomerFormLoginWebSecurity extends WebSecurityConfigurerAdapter {
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/", "/signin/**", "/error/**", "/templates/**", "/resources/**", "/webjars/**");
}
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/customer/**").hasRole("CUSTOMER")
.and()
.formLogin()
.loginPage("/customer_signin")
.failureUrl("/customer_signin?error=1")
.defaultSuccessUrl("/customer/home")
.loginProcessingUrl("/j_spring_security_check")
.usernameParameter("j_username").passwordParameter("j_password")
.and()
.logout()
.permitAll();
http.exceptionHandling().accessDeniedPage("/customer_signin");
}
}
#Configuration
public static class AdminFormLoginWebSecurity extends WebSecurityConfigurerAdapter {
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/", "/signin/**", "/error/**", "/templates/**", "/resources/**", "/webjars/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.and()
.formLogin()
.loginPage("/admin_signin")
.failureUrl("/admin_signin?error=1")
.defaultSuccessUrl("/admin/home")
.loginProcessingUrl("/j_spring_security_check")
.usernameParameter("j_username").passwordParameter("j_password")
.and()
.logout()
.permitAll();
http.exceptionHandling().accessDeniedPage("/admin_signin");
}
}
}
The component of the spring login chain that redirects to a login page is the authentication filter, and the filter that get's plugged in when using http.formLogin() is DefaultLoginPageGeneratingFilter.
This filter either redirects to the login url or builds a default basic login page, if no login page url is provided.
What you need then is a custom authentication filter with the logic to define which login page is needed, and then plug it in the spring security chain in place of the single page authentication filter.
Consider creating a TwoPageLoginAuthenticationFilter by subclassing DefaultLoginPageGeneratingFilter and overriding getLoginPageUrl(), and if that is not sufficient then copy the code and modify it to meet your needs.
This filter is a GenericFilterBean, so you can declare it like this:
#Bean
public Filter twoPageLoginAuthenticationFilter() {
return new TwoPageLoginAuthenticationFilter();
}
then try building only one http configuration and don't set formLogin(), but instead do:
http.addFilterBefore(twoPageLoginAuthenticationFilter, ConcurrentSessionFilter.class);
and this will plug the two form authentication filter in the right place in the chain.
The solution that I have come to for multiple login pages involves a single http authentication but I provide my own implementations of
AuthenticationEntryPoint
AuthenticationFailureHandler
LogoutSuccessHandler
What I needed was for these implementations to be able to switch dependent on a token in the request path.
In my website the pages with a customer token in the url are protected and require a user to authenticate as CUSTOMER at the customer_signin page.
So if wanted to goto a page /customer/home then I need to be redirected to the customer_signin page to authenticate first.
If I fail to authenticate on customer_signin then I should be returned to the customer_signin with an error paramater. So that a message can be displayed.
When I am successfully authenticated as a CUSTOMER and then wish to logout then the LogoutSuccessHandler should take me back to the customer_signin page.
I have a similar requirement for admins needing to authenticate at the admin_signin page to access a page with an admin token in the url.
First I defined a class that would allow me to take a list of tokens (one for each type of login page)
public class PathTokens {
private final List<String> tokens = new ArrayList<>();
public PathTokens(){};
public PathTokens(final List<String> tokens) {
this.tokens.addAll(tokens);
}
public boolean isTokenInPath(String path) {
if (path != null) {
for (String s : tokens) {
if (path.contains(s)) {
return true;
}
}
}
return false;
}
public String getTokenFromPath(String path) {
if (path != null) {
for (String s : tokens) {
if (path.contains(s)) {
return s;
}
}
}
return null;
}
public List<String> getTokens() {
return tokens;
}
}
I then use this in PathLoginAuthenticationEntryPoint to change the login url depending on the token in the request uri.
#Component
public class PathLoginAuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoint {
private final PathTokens tokens;
#Autowired
public PathLoginAuthenticationEntryPoint(PathTokens tokens) {
// LoginUrlAuthenticationEntryPoint requires a default
super("/");
this.tokens = tokens;
}
/**
* #param request the request
* #param response the response
* #param exception the exception
* #return the URL (cannot be null or empty; defaults to {#link #getLoginFormUrl()})
*/
#Override
protected String determineUrlToUseForThisRequest(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) {
return getLoginUrlFromPath(request);
}
private String getLoginUrlFromPath(HttpServletRequest request) {
String requestUrl = request.getRequestURI();
if (tokens.isTokenInPath(requestUrl)) {
return "/" + tokens.getTokenFromPath(requestUrl) + "_signin";
}
throw new PathTokenNotFoundException("Token not found in request URL " + requestUrl + " when retrieving LoginUrl for login form");
}
}
PathTokenNotFoundException extends AuthenticationException so that you can handle it in the usual way.
public class PathTokenNotFoundException extends AuthenticationException {
public PathTokenNotFoundException(String msg) {
super(msg);
}
public PathTokenNotFoundException(String msg, Throwable t) {
super(msg, t);
}
}
Next I provide an implementation of AuthenticationFailureHandler that looks at the referer url in the request header to determine which login error page to direct the user to.
#Component
public class PathUrlAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
private final PathTokens tokens;
#Autowired
public PathUrlAuthenticationFailureHandler(PathTokens tokens) {
super();
this.tokens = tokens;
}
/**
* Performs the redirect or forward to the {#code defaultFailureUrl associated with this path} if set, otherwise returns a 401 error code.
* <p/>
* If redirecting or forwarding, {#code saveException} will be called to cache the exception for use in
* the target view.
*/
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
setDefaultFailureUrl(getFailureUrlFromPath(request));
super.onAuthenticationFailure(request, response, exception);
}
private String getFailureUrlFromPath(HttpServletRequest request) {
String refererUrl = request.getHeader("Referer");
if (tokens.isTokenInPath(refererUrl)) {
return "/" + tokens.getTokenFromPath(refererUrl) + "_signin?error=1";
}
throw new PathTokenNotFoundException("Token not found in referer URL " + refererUrl + " when retrieving failureUrl for login form");
}
}
Next I provide an implementation of LogoutSuccessHandler that will logout the user and redirect them to the correct signin page depending on the token in ther referer url in the request header.
#Component
public class PathUrlLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
private final PathTokens tokens;
#Autowired
public PathUrlLogoutSuccessHandler(PathTokens tokens) {
super();
this.tokens = tokens;
}
#Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
setDefaultTargetUrl(getTargetUrlFromPath(request));
setAlwaysUseDefaultTargetUrl(true);
handle(request, response, authentication);
}
private String getTargetUrlFromPath(HttpServletRequest request) {
String refererUrl = request.getHeader("Referer");
if (tokens.isTokenInPath(refererUrl)) {
return "/" + tokens.getTokenFromPath(refererUrl) + "_signin";
}
throw new PathTokenNotFoundException("Token not found in referer URL " + refererUrl + " when retrieving logoutUrl.");
}
}
The final step is to wire them all together in the security configuration.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired PathLoginAuthenticationEntryPoint loginEntryPoint;
#Autowired PathUrlAuthenticationFailureHandler loginFailureHandler;
#Autowired
PathUrlLogoutSuccessHandler logoutSuccessHandler;
#Bean
public PathTokens pathTokens(){
return new PathTokens(Arrays.asList("customer", "admin"));
}
#Autowired
public void registerGlobalAuthentication(
AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("customer").password("password").roles("CUSTOMER").and()
.withUser("admin").password("password").roles("ADMIN");
}
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/", "/signin/**", "/error/**", "/templates/**", "/resources/**", "/webjars/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http .csrf().disable()
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/customer/**").hasRole("CUSTOMER")
.and()
.formLogin()
.loginProcessingUrl("/j_spring_security_check")
.usernameParameter("j_username").passwordParameter("j_password")
.failureHandler(loginFailureHandler);
http.logout().logoutSuccessHandler(logoutSuccessHandler);
http.exceptionHandling().authenticationEntryPoint(loginEntryPoint);
http.exceptionHandling().accessDeniedPage("/accessDenied");
}
}
Once you have this configured you need a controller to to direct to the actual signin page. The SigninControiller below checks the queryString for a value that would indicate a signin error and then sets an attribute used to control an error message.
#Controller
#SessionAttributes("userRoles")
public class SigninController {
#RequestMapping(value = "customer_signin", method = RequestMethod.GET)
public String customerSignin(Model model, HttpServletRequest request) {
Set<String> userRoles = AuthorityUtils.authorityListToSet(SecurityContextHolder.getContext().getAuthentication().getAuthorities());
model.addAttribute("userRole", userRoles);
if(request.getQueryString() != null){
model.addAttribute("error", "1");
}
return "signin/customer_signin";
}
#RequestMapping(value = "admin_signin", method = RequestMethod.GET)
public String adminSignin(Model model, HttpServletRequest request) {
Set<String> userRoles = AuthorityUtils.authorityListToSet(SecurityContextHolder.getContext().getAuthentication().getAuthorities());
model.addAttribute("userRole", userRoles);
if(request.getQueryString() != null){
model.addAttribute("error", "1");
}
return "signin/admin_signin";
}
}
Maybe this post could help you :
Multiple login forms
It's a different version of spring security but the same problem : only the first configuration is taken.
It seems it has been solved by changing login-processing-url for one of the two login pages but people suggest to use the same url processing but a different layout using ViewResolver. It is a solution if you use the same mechanism to authenticate users (the authentication mechanism is the thing responsible for processing the credentials that the browser is sending).
This post also seems to say that if you change your loginProcessingUrl you will succeed :
Configuring Spring Security 3.x to have multiple entry points
I also encountered this problem and found out that I missed the first filtering part.
This one:
http.csrf().disable()
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
Should be:
http.csrf().disable()
.antMatcher("/admin/**")
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
Adding the first filtering .antMatcher("/admin/**") will first filter it so that it will use the AdminFormLoginWebSecurity instead of the other one.

Spring security - Custom ExceptionTranslationFilter

This question is actually related to this issue problem.
Based on the suggestion from #harsh-poddar, I added the filter accordingly.
However, after adding that it seems like I can't login even with valid credential.
Following is the related code:
SecurityConfig
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// #Bean
// public CustomAuthenticationEntryPoint customAuthenticationEntryPoint() {
// return new CustomAuthenticationEntryPoint();
// }
#Bean
public CustomExceptionTranslationFilter customExceptionTranslationFilter() {
return new CustomExceptionTranslationFilter(new CustomAuthenticationEntryPoint());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
//Note : Able to login without this filter, but after adding this, valid credential also fails
.addFilterAfter(customExceptionTranslationFilter(), ExceptionTranslationFilter.class)
// .exceptionHandling()
// .authenticationEntryPoint(new customAuthenticationEntryPoint())
// .and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.requestCache()
.requestCache(new NullRequestCache())
.and()
.httpBasic()
.and()
.csrf().disable();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(new CustomAuthenticationProvider());
}
}
CustomAuthenticationProvider
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
public CustomAuthenticationProvider() {
super();
}
#Override
public Authentication authenticate(final Authentication authentication) throws AuthenticationException {
final String name = authentication.getName();
final String password = authentication.getCredentials().toString();
if (name.equals("admin") && password.equals("password")) {
final List<GrantedAuthority> grantedAuths = new ArrayList<>();
grantedAuths.add(new SimpleGrantedAuthority("ROLE_USER"));
final UserDetails principal = new User(name, password, grantedAuths);
final Authentication auth = new UsernamePasswordAuthenticationToken(principal, password, grantedAuths);
return auth;
} else {
throw new BadCredentialsException("NOT_AUTHORIZED");
}
}
#Override
public boolean supports(final Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
CustomExceptionTranslationFilter
#Component
public class CustomExceptionTranslationFilter extends ExceptionTranslationFilter {
public CustomExceptionTranslationFilter(AuthenticationEntryPoint authenticationEntryPoint) {
super(authenticationEntryPoint);
}
}
CustomAuthenticationEntryPoint
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized.");
}
}
p/s : sorry for the basic question, I'm really new in spring & spring security.
The intended design for AuthenticationEntryPoint is to start/initiate an authentication. However, your implementation CustomAuthenticationEntryPoint does not do this. Instead, it simply sends back an unauthorized response. Please see javadoc for AuthenticationEntryPoint for more details on implementation specifics.
Based on your configuration you are using HTTP Basic for authentication:
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic();
}
This specific configuration will automatically configure BasicAuthenticationEntryPoint which is an implementation of AuthenticationEntryPoint. The BasicAuthenticationEntryPoint will challenge the user with a http response header of WWW-Authenticate: Basic realm="User Realm" to authenticate, as per server protocol.
However, the fact that you are configuring your own CustomAuthenticationEntryPoint it will ultimately override the BasicAuthenticationEntryPoint which is not what you want to do.
The other post recommended this configuration which again is not what you want to do.
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic()
.and()
.exceptionHandling().authenticationEntryPoint(new CustomAuthenticationEntryPoint());
}
If your main goal is to provide a custom response to the user when authentication fails than I would propose a form login configuration with a configured AuthenticationFailureHandler. Here is the configuration:
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().failureHandler(new DefaultAuthenticationFailureHandler())
.and()
.csrf().disable(); // NOTE: I would recommend enabling CSRF
Your implementation of DefaultAuthenticationFailureHandler would be:
public class DefaultAuthenticationFailureHandler implements AuthenticationFailureHandler {
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
// Set status only OR do whatever you want to the response
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
}
}
The AuthenticationFailureHandler is specifically designed to handle a failed authentication attempt.

Categories

Resources