I have following code with me
I am trying to achieve ldap Authentication but i think it is not happening.
My Security Configuration
#EnableWebSecurity
#Configuration
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class Config extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().and().authorizeRequests().antMatchers("/*")
.permitAll().anyRequest().authenticated().and().csrf()
.disable().httpBasic().and().csrf()
.csrfTokenRepository(csrfTokenRepository()).and()
.addFilterAfter(csrfHeaderFilter(), CsrfFilter.class);
}
#Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.ldapAuthentication()
.userSearchFilter("(uid={0})")
.userSearchBase("dc=intern,dc=xyz,dc=com")
.contextSource()
.url("ldap://192.168.11.11:1234/dc=intern,dc=xyz,dc=com")
.managerDn("username")
.managerPassword("password!")
.and()
.groupSearchFilter("(&(objectClass=user)(sAMAccountName=" + "username" + "))");
}
private Filter csrfHeaderFilter() {
return new OncePerRequestFilter() {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
CsrfToken csrf = (CsrfToken) request
.getAttribute(CsrfToken.class.getName());
if (csrf != null) {
Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
String token = csrf.getToken();
if (cookie == null || token != null
&& !token.equals(cookie.getValue())) {
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
response.sendRedirect("/notAllowed");
}
}
filterChain.doFilter(request, response);
}
};
}
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
}
My Controller
#RequestMapping(value = { "/test" }, method = RequestMethod.GET)
public #ResponseBody String retrieve() {
System.out.println("line 1");
System.out.println("line 2");
return "hello";
}
#RequestMapping(value = { "/notAllowed" }, method = RequestMethod.GET)
public #ResponseBody HttpStatus login() {
return HttpStatus.FORBIDDEN;
}
i am aiming for :
i want to achieve ldap authentication. Username and password will come from browser though i have tried with hardcoded username and password as well.
if user is authentic then filter will check the authorizátion by checking the token .
if this is first request then new token will be generated and sent.
if its not found then it will send the HTTP Status forbidden.
I have following problems :
when i run first time from browser it returns forbidden but it also prints "line 1 and line 2" in console though it do not return hello but forbidden.
are my htpSecurity and ldap Configuration fine?.
from 2nd request it always return hello , i have tried to open new tab ,new request but still it works fine .If i restart server then only it generates token and compare it with cookies token.what if two people are using same system (different times).
how exactly i can test ldap authentication ? i am using POSTMAN as a client .
If some information is missing from my end please let me know .
And i will be thankful for your answers.
First of all, I think your HttpSecurity config is wrong. You want to protect ALL the endpoints. Don't you?
So change it to the following:
http.httpBasic()
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.csrf()
.csrfTokenRepository(csrfTokenRepository())
.and()
.addFilterAfter(csrfHeaderFilter(), CsrfFilter.class);
Furthermore, I'm not sure whether your ldap config is right. I think you can reduce it to the following:
auth.ldapAuthentication()
.userSearchFilter("uid={0}")
.contextSource()
.url("ldap://192.168.11.11:1234/dc=intern,dc=xyz,dc=com");
Make sure if your userSearchBase is right. It doesn't have an "ou".
If you don't have any different organizational units, you can simply remove the userSearchBase
To provide better help i need to know the structure of your ldap.
If you want to check your HttpSecurity config you may not use ldap in the first place and use inMemoryAuthentication instead:
auth.inMemoryAuthentication().withUser("user").password("password").authorities("ROLE_USER");
Related
I am working on a spring boot + spring security based application. I have used jdbcAuthentication to validate user. I have also configured custom login form.
After running the application I am able to successfully login and get the API response through browser but when I try to test the API using Postman I only get the HTML login page as response. How do I get the desired API json response?
My configuration file:
#Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
System.out.println("auth manager called");
auth. jdbcAuthentication() .usersByUsernameQuery(usersQuery)
.authoritiesByUsernameQuery(rolesQuery) .dataSource(dataSource)
.passwordEncoder(noop);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
System.out.println("Http scurity called");
http.httpBasic().
and().
authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/login").permitAll()
.antMatchers("/registration").permitAll()
.antMatchers("/admin/**").hasAuthority("ADMIN")
.antMatchers("/db").hasAuthority("DBA")
.antMatchers("/user").hasAuthority("USER").anyRequest()
.authenticated().and().csrf().disable().formLogin()
.loginPage("/login").failureUrl("/login?error=true")
.successHandler(customSuccessHandler)
.usernameParameter("username")
.passwordParameter("password")
.and().logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/").and().exceptionHandling()
.accessDeniedPage("/access-denied");
}
My Controller file:
#RequestMapping(value = { "/", "/login" }, method = RequestMethod.GET)
public ModelAndView login() {
System.out.println("/login called");
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("login");
return modelAndView;
}
#RequestMapping(value = "/admin", method = RequestMethod.GET, produces = { "application/json" })
public UserUniconnect home(HttpServletRequest request, HttpServletResponse response) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String currentUser = null;
if (!(auth instanceof AnonymousAuthenticationToken)) {
currentUser = auth.getName();
}
User user1 = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
user1.getAuthorities();
System.out.println("++++++++++++++++++++++++++++++");
System.out.println(request == null);
Users u = (Users) request.getSession(false).getAttribute("user");
Uniconnect uni = (Uniconnect) request.getSession(false).getAttribute("uniconnect");
UserUniconnect uu = new UserUniconnect();
uu.setUser(u);
uu.setUniconnect(uni);
return uu;
}
I am returning java object as the response which spring boot is able to convert it into json format.
Postman Screenshot
Setting up the Basic Auth parameters in Postman might help:
It is most likely that you need to get your session id from a cookie after logging in manually with your browser and then provide this cookie to Postman just like this:
Getting a cookie from browser differs depending on a browser itself, but Chrome and Firefox both have a Developer utils built in, so that should not be a problem.
In my Java Spring application I have implemented OAuth2 user authorization via external OAuth2 provider.
At my localhost in order to authenticate user via this external OAuth2 provider I need to go by the following url: https://127.0.0.1:8443/login/ok and right after OAuth2 dance I can get this user authenticated. So far everything is ok.
But when I have some request parameters in my login url, for example uid and level:
https://127.0.0.1:8443/login/ok?uid=45134132&level=3
after OAuth2 dance I'm redirected to https://127.0.0.1:8443/ and lose those parameters.
In my Chrome network panel I can see following set of calls:
https://127.0.0.1:8443/login/ok?uid=45134132&level=3
https://connect.ok.ru/oauth/authorize?redirect_uri=https://127.0.0.1:8443/login/ok?uid%3D45134132%26level%3D3&response_type=code&state=AKakq....
https://127.0.0.1:8443/login/ok?uid=45134132&level=3&code=....
https://127.0.0.1:8443/
So I'm losing these parameters after step #3.
Is it possible to configure Spring Security + OAuth2 to pass these parameters to step #4 also ?
This is my config(this a solution based on this answer Spring Security - Retaining URL parameters on redirect to login) but it doesn't work(AuthenticationProcessingFilterEntryPoint .commence method is not invoked):
#Override
public void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.headers().frameOptions().disable()
.and().logout()
.and().antMatcher("/**").authorizeRequests()
.antMatchers("/", "/login**", "/index.html", "/home.html").permitAll()
.anyRequest().authenticated()
.and().exceptionHandling().authenticationEntryPoint(new AuthenticationProcessingFilterEntryPoint("/"))
.and().logout().logoutSuccessUrl("/").permitAll()
.and().csrf().csrfTokenRepository(csrfTokenRepository())
.and().addFilterAfter(csrfHeaderFilter(), CsrfFilter.class)
.addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class);
// #formatter:on
}
public class AuthenticationProcessingFilterEntryPoint extends LoginUrlAuthenticationEntryPoint {
public AuthenticationProcessingFilterEntryPoint(String loginFormUrl) {
super(loginFormUrl);
}
#Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
redirectStrategy.sendRedirect(request, response, getLoginFormUrl() + "?" + request.getQueryString());
}
}
What can be wrong ?
I have implemented this in the following way:
private Filter ssoFilter(ClientResources client, String path) {
OAuth2ClientAuthenticationProcessingFilter clientFilter = new OAuth2ClientAuthenticationProcessingFilter(path);
.......
clientFilter.setAuthenticationSuccessHandler(new UrlParameterAuthenticationHandler());
return clientFilter;
}
public class UrlParameterAuthenticationHandler extends SimpleUrlAuthenticationSuccessHandler {
#Override
protected void handle(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
String targetUrl = determineTargetUrl(request, response);
if (response.isCommitted()) {
logger.debug("Response has already been committed. Unable to redirect to " + targetUrl);
return;
}
String queryString = HttpUtils.removeParams(request.getQueryString(), "state", "code");
targetUrl = !StringUtils.isEmpty(queryString) ? targetUrl + "?" + queryString : targetUrl;
getRedirectStrategy().sendRedirect(request, response, targetUrl);
}
}
Please correct me if there is a better approach
I know this question can be found with different solutions. But I am unable to get it working in my project.
We are sending mails to users which has link to perform some action in the application. When user click on url he should be redirect to login page if he is not logged in and after login should be navigated to the targeted URL.
I am trying to fix using CustomLoginSuccessHandler here is the code.:
public class CustomLoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
// public CustomLoginSuccessHandler(String defaultTargetUrl) {
// setDefaultTargetUrl(defaultTargetUrl);
// }
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
HttpSession session = request.getSession(false);
if (session != null) {
String redirectUrl = (String) session.getAttribute("url_prior_login");
if (redirectUrl != null) {
// we do not forget to clean this attribute from session
session.removeAttribute("url_prior_login");
// then we redirect
getRedirectStrategy().sendRedirect(request, response, redirectUrl);
} else {
super.onAuthenticationSuccess(request, response, authentication);
}
} else {
super.onAuthenticationSuccess(request, response, authentication);
}
}
}
Configurations I am using are :
#Bean
public SavedRequestAwareAuthenticationSuccessHandler authenticationSuccessHandler(){
CustomLoginSuccessHandler successHandler = new CustomLoginSuccessHandler();
// SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
// successHandler.setUseReferer(true); getting NULL in the controller every time
// successHandler.setTargetUrlParameter("targetUrl"); this also doesnt work as browser is redirect to /login page and URL parameters are lost
return successHandler;
}
protected void configure(HttpSecurity http) throws Exception {
http
.logout().logoutUrl("/logout").deleteCookies("JSESSIONID").logoutSuccessUrl("/logoutSuccess")
.and()
.authorizeRequests()
.antMatchers("/privacyPolicy", "/faq", "/aboutus", "/termsofuse", "/feedback","/feedbackSubmit", "/contactSsm", "/resources/**", "/userReply", "/userReplySubmit", "/image", "/logoutExternal", "/closeit").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.successHandler(authenticationSuccessHandler)
.loginPage("/login")
.defaultSuccessUrl("/")
.permitAll();
// .and().exceptionHandling().authenticationEntryPoint(new CustomAuthenticationEntryPoint());
}
Problem using this configuration is, If i request for url say 'http:localhost:8080/showPage' spring security is navigating to 'http:localhost:8080/login' and I am unable to capture anything from original URL. Same problem occurs when I try to use a custom variable targetUrl and using it in the same CustomLoginSuccessHandler.
Please let me know if am taking a wrong approach or something else is missing
Also tried using Custom EntryPoint but unable to redirect using my entrypoint.
#Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint{
private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
request.getSession().setAttribute("targetUrl",request.getRequestURL());
redirectStrategy.sendRedirect(request,response,request.getRequestURL().toString());
}
}
Controller :
#RequestMapping(value="/login")
public ModelAndView loginHandler(HttpServletRequest request) {
ModelAndView mav = new ModelAndView();
String targetUrl = request.getParameter("targetUrl");
if(targetUrl!=null){ // targetUrl is always null as spring security is navigating to /login asd parameters are lost
request.getSession().setAttribute("url_prior_login",targetUrl);
}
mav.setViewName("login");
return mav;
}
To login, page is navigated to a different domain. and I pass a redirect URL to that domain after successful login it redirects the page back to the redirecturl
<a href="https://domain/sso/identity/login?channel=abc&ru=${externalUrl.applicationUrl}login" >Sign In</a>
Spring Security already stores the request using a RequestCache the default implementation HttpSessionRequestCache stores the last request in the HTTP session. You can access it using the SPRING_SECURITY_SAVED_REQUEST attribute name to get it from the session.
Doing something like this in your controller
public ModelAndView login(HttpServletRequest req, HttpSession session) {
ModelAndView mav = new ModelAndView("login");
if (session != null) {
SavedRequest savedRequest = session.getAttribute("SPRING_SECURITY_SAVED_REQUEST");
if (savedRequest != null) {
mav.addObject("redirectUrl", savedRequest.getRedirectUrl());
}
}
return mav;
}
Then in your JSP you can use the redirectUrl to dynamically construct your URL.
http://your.sso/login?url=${redirectUrl}
The final thing you need to do is to make /login accessible for everyone by adding it to the list which is protected by permitAll(). If you don't do this, you will get into a loop or the last request is overwritten and will always point to the login page.
.antMatchers("/privacyPolicy", "/faq", "/aboutus", "/termsofuse", "/feedback","/feedbackSubmit", "/contactSsm", "/resources/**", "/userReply", "/userReplySubmit", "/image", "/logoutExternal", "/closeit", "/login").permitAll()
You don't need any other custom classes like EntryPoints or AuthenticationSuccessHandler implementations.
However as you are using SSO it would be probably best to investigate a proper integration with the SSO solution instead of this hack with a login page.
You will at least have one problem : HttpSession session = request.getSession();.
getSession()
Returns the current session associated with this request, or if the request does not have a session, creates one.
You should use getSession(false) if you want a null return in case there is no session.
In your case you'll never get a null session.
I had the same issue and have solved it by using SavedRequestAwareAuthenticationSuccessHandler as a successHandler to make Spring handle the saved request that was requested before redirecting to login page when user is not logged.
In WebSecurityConfigurerAdapter:
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private static final String LOGIN_PATH = "/login";
#Autowired
MyApplicationAuthenticationSuccessHandler myApplicationAuthenticationSuccessHandler;
#Override
protected void configure(HttpSecurity http) throws Exception {
// Set the default URL when user enters a non internal URL (Like https://my-application.com)
myApplicationAuthenticationSuccessHandler.setDefaultTargetUrl("/myapp/home");
http.authorizeRequests().antMatchers("/resources/**").permitAll().antMatchers(LOGIN_PATH).permitAll().antMatchers("/auto/**").authenticated()
.and().formLogin().loginPage(LOGIN_PATH).permitAll()
.successHandler(myApplicationAuthenticationSuccessHandler).and().logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl(LOGIN_PATH)
.invalidateHttpSession(true).deleteCookies("JSESSIONID").permitAll().and().sessionManagement().invalidSessionUrl(LOGIN_PATH);
}
}
In custom SavedRequestAwareAuthenticationSuccessHandler:
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
#Component("myApplicationAuthenticationSuccessHandler")
public class MyApplicationAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
#Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException {
try {
super.onAuthenticationSuccess(request, response, authentication);
} catch (ServletException e) {
// redirect to default page (home in my case) in case of any possible problem (best solution in my case)
redirectStrategy.sendRedirect(request, response, "/myapp/home");
}
}
}
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.**
}
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.