Using Spring-Boot 1.1.17, Spring-MVC with Spring-Security:
I have several subdomains that I want to to allow unauthenticated users (Visitors) access to. For example:
mysite.com/customerA
mysite.com/customerB
If a invalid customer site is attempted, then my controller would either throw an exception or redirect back to / (mysite.com/) Naturally other parts of the domain (mysite.com/customerA/myaccount) will require login.
I haven't really figured out how to do this with spring security and spring-mvc. Here is what I am attempting so far:
#Configuration
#EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomUserDetailsService customUserDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterAfter(new CSRFTokenGeneratorFilter(), CsrfFilter.class)
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers( "/**/" ).permitAll()
.antMatchers("/login").permitAll()
.antMatchers("/wizard").permitAll()
.antMatchers("/menu").permitAll()
.antMatchers("/error").permitAll()
.antMatchers("/resources/**").permitAll()
.antMatchers("/css/**").permitAll()
.antMatchers("/js/**").permitAll()
.antMatchers("/fonts/**").permitAll()
.antMatchers("/libs/**").permitAll();
http
.formLogin()
.loginPage("/loginPage")
.permitAll()
.loginProcessingUrl("/login")
.failureUrl("/login?error")
.defaultSuccessUrl("/?tab=success")
.and()
.logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/")
.permitAll()
.and()
.csrf();
http
.sessionManagement()
.maximumSessions(1)
.expiredUrl("/login?expired")
.maxSessionsPreventsLogin(true)
.and()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.invalidSessionUrl("/");
http
.authorizeRequests().anyRequest().authenticated();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
PasswordEncoder encoder = new BCryptPasswordEncoder();
auth.userDetailsService( customUserDetailsService ).passwordEncoder( encoder );
}
#Override
public void configure(WebSecurity security){
security.ignoring().antMatchers("/css/**","/fonts/**","/libs/**");
}
}
And my homepage controller:
#Controller
#RequestMapping("/{officeName}/")
public class HomeController {
private AuthenticatedUser getVisitor(#PathVariable String officeName) {
.. do something with the office if found, redirect otherwise
if (!StringUtils.isEmpty(officeName)) {
Office office = officeService.findByName( officeName );
return office.getUrl();
}
return "/";
}
When I try to access that url, I get the following errors:
o.s.web.servlet.DispatcherServlet : DispatcherServlet with name 'dispatcherServlet' processing GET request for [/customerA/]
s.w.s.m.m.a.RequestMappingHandlerMapping : Looking up handler method for path /customerA/
s.w.s.m.m.a.RequestMappingHandlerMapping : Did not find handler method for [/customerA/]
o.s.w.s.handler.SimpleUrlHandlerMapping : Matching patterns for request [/customerA/] are [/**]
o.s.w.s.handler.SimpleUrlHandlerMapping : URI Template variables for request [/customerA/] are {}
o.s.w.s.handler.SimpleUrlHandlerMapping : Mapping [/customerA/] to HandlerExecutionChain with handler [org.springframework.web.servlet.resource.ResourceHttpRequestHandler#2f295527] and 1 interceptor
o.s.web.servlet.DispatcherServlet : Last-Modified value for [/customerA/] is: -1
o.s.w.s.r.ResourceHttpRequestHandler : Trying relative path [customerA] against base location: ServletContext resource [/]
o.s.w.s.r.ResourceHttpRequestHandler : Trying relative path [customerA] against base location: class path resource [META-INF/resources/]
o.s.w.s.r.ResourceHttpRequestHandler : Trying relative path [customerA] against base location: class path resource [resources/]
o.s.w.s.r.ResourceHttpRequestHandler : Trying relative path [customerA] against base location: class path resource [static/]
o.s.w.s.r.ResourceHttpRequestHandler : Trying relative path [customerA] against base location: class path resource [public/]
o.s.w.s.r.ResourceHttpRequestHandler : No matching resource found - returning 404
I tried adding this ServletRegistrationBean:
#Bean
public ServletRegistrationBean dispatcherRegistration(DispatcherServlet dispatcherServlet) {
ServletRegistrationBean registration = new ServletRegistrationBean( dispatcherServlet );
registration.addUrlMappings("/", "/testCustomer/*" );
for ( Office office : officeService.findAllActiveOffices() ) {
registration.addUrlMappings( office.getUrl() + "/*" );
}
return registration;
}
But this would seem to only work if the application knows of the customer at startup, not dynamically in the case of customer signup.
Is there a way to configure this to handle these types of wildcards?
You can try with a configuration like the following:
#Configuration
#EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService _userService;
#Autowired
private PasswordEncoder _passwordEncoder;
/**
* Defines the password encoder used by Spring security during the
* authentication procedure.
*/
#Bean
public PasswordEncoder passwordEncoder() {
// default strength = 10
return new BCryptPasswordEncoder();
}
/**
* Sets security configurations for the authentication manager
*/
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth)
throws Exception {
auth
.userDetailsService(_userService)
.passwordEncoder(_passwordEncoder);
return;
}
/**
* Configures where Spring Security will be disabled (security = none).
* From spring reference: "Typically the requests that are registered [here]
* should be that of only static resources. For requests that are dynamic,
* consider mapping the request to allow all users instead."
*/
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(
"/css/**",
"/js/**",
"/fonts/**",
"/resources/**",
"/libs/**");
return;
}
/**
* Sets security configurations in the HttpSecurity object.
*/
#Override
protected void configure(HttpSecurity http) throws Exception {
// Set security configurations
http
.authorizeRequests()
// the following urls are allowed for any user (no authentication)
.antMatchers(
"/",
"/login",
"/menu")
.permitAll()
// any other url must be authenticated
.anyRequest().authenticated()
.and()
// define the login page url
.formLogin()
.loginPage("/login")
.permitAll()
.and()
// define the logout url
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/login?logout")
.permitAll();
return;
} // method configure
} // class WebSecurityConfig
Adding your personal configurations... You can try to add the following controller:
#Controller
public class HomeController {
#RequestMapping("/{officeName}/")
public AuthenticatedUser getVisitor(#PathVariable String officeName) {
// .. do something with the office if found, redirect otherwise
if (!StringUtils.isEmpty(officeName)) {
Office office = officeService.findByName( officeName );
return office.getUrl();
}
return "/";
}
}
If the user is correctly authenticated he should access the url at the officeName.
Related
This question already has answers here:
Spring Security : Multiple HTTP Config not working
(2 answers)
Closed 1 year ago.
I have the following configuration:
#Configuration
#EnableWebSecurity
public class SecurityConfig {
#Configuration
#Order(1)
public static class SamlConfig extends WebSecurityConfigurerAdapter {
#Value("${enable_csrf}")
private Boolean enableCsrf;
#Autowired
private SamlUserService samlUserService;
public SamlWebSecurityConfig() {
super();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/secure/sso").permitAll()
.antMatchers("/saml/**").permitAll()
.anyRequest().authenticated()
.and()
.apply(saml())
.userDetailsService(samlUserService)
.serviceProvider()
.keyStore()
.storeFilePath("path")
.password("password")
.keyname("alias")
.keyPassword("password")
.and()
.protocol("https")
.hostname(String.format("%s:%s","localhost", "8080"))
.basePath("/")
.and()
.identityProvider()
.metadataFilePath("metadata");
if (!enableCsrf) {
http.csrf().disable();
}
}
}
#Configuration
#Order(2)
public static class BasicConfig extends WebSecurityConfigurerAdapter {
public BasicWebSecurityConfig() {
super();
}
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/secure/basic").permitAll()
.anyRequest().authenticated();
if (!enableCsrf) {
http.csrf().disable();
}
}
}
This works for the SAML, but the basic login returns an error: 403 forbidden.
I modified the BasicConfig with this, and SAML doesn't work anymore but basic authentication works. All the endpoints are for both SAML and basic authentication, just different login page.
public static class BasicConfig extends WebSecurityConfigurerAdapter {
public BasicWebSecurityConfig() {
super();
}
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/secure/basic").permitAll()
.antMatchers("/**").permitAll()
.anyRequest().authenticated();
if (!enableCsrf) {
http.csrf().disable();
}
}
}
For some reasons sometimes it works, sometimes not. I also tried to modify the #Order and still not working.
In Spring Security, there are two things that are alike but do things completely differently, requestMatchers().antMatchers() and authorizeRequests().antMatchers().
The requestMatchers tells HttpSecurity to only invoke the SecurityFilterChain if the provided RequestMatcher was matched.
The authorizeRequests allows restricting access based upon the HttpServletRequest using RequestMatcher implementations.
In your case, you have two SecurityFilterChains. But only the one with the highest priority is being invoked, this happens because you did not give any requestMatchers to it, therefore it will match every request. And only one SecurityFilterChain is called per request, thus it will not invoke the next one.
So, you should inform the requestMatchers for your configurations, like so:
http
.requestMatchers((requests) -> requests
.antMatchers("/secure/sso", "/saml/**")
)
.authorizeRequests()
.antMatchers("/secure/sso").permitAll()
.antMatchers("/saml/**").permitAll()
.anyRequest().authenticated()
...
http
.requestMatchers((requests) -> requests
.antMatchers("/secure/basic", "/**")
)
.authorizeRequests()
.antMatchers("/secure/basic").permitAll()
.anyRequest().authenticated();
I have added "spring-boot-starter-actuator" dependency to my spring-boot project.
The project already has form based security.
The root context for the application is "/".
I have added the actuator at the context root "/actuators" by adding to application.yaml:
management:
context-path: /actuators
The non-sensitive actuators are working, such as "health".
When I try to access the sensitive actuators, the popup appears for username/password. The authentication takes place, but then I see "403" Access is Denied.
Here is the configuration for Web security:
#Configuration
#EnableWebSecurity
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private AuthenticationLookupService authenticationLookupService;
private AuthenticationManagerBuilder authenticationManagerBuilder;
private UrlSuccessAuthenticationHandler successHandler;
#Autowired
public void setAuthenticationLookupService(AuthenticationLookupService authenticationLookupService) {
this.authenticationLookupService = authenticationLookupService;
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
this.authenticationManagerBuilder = auth;
}
#Autowired
public void setSuccessHandler(UrlSuccessAuthenticationHandler successHandler) {
this.successHandler = successHandler;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/", "/index.html", "/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/index.html").successHandler(successHandler)
.permitAll()
.and()
.logout()
.permitAll();
http.csrf().disable(); // todo: fix later
}
#PostConstruct
public void process() throws Exception {
this.authenticationManagerBuilder.userDetailsService(this.authenticationLookupService).passwordEncoder(new ShaPasswordEncoder());
}
}
Add below values in application.properties or application.yml and then when popup asks for username and password provide this credentials
security.user.name=admin
security.user.password=secret
If you are providing your values check if that user has ADMIN role, because actuator needs ADMIN role user to access sensitive end points.
Update
If you are using spring-boot 1.5.*+ then user should have ACTUATOR role
I want to set up custom AuthenticationFailureHandler in my project. Even though I configure my authenticationFailureHandler as below , it is not properly picked up when the sign-in fails.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
..................
#Inject
private AjaxAuthenticationFailureHandler ajaxAuthenticationFailureHandler;
..................
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/resources/**").permitAll()
.anyRequest().authenticated().and()
.formLogin().loginPage("/signin").failureHandler(ajaxAuthenticationFailureHandler)
.permitAll()
.failureUrl("/signin")
.defaultSuccessUrl("/search").and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/signout"))
.logoutSuccessUrl("/signin")
.permitAll().and().csrf();
}
My custom AuthenticationFailureHandler Class
#Component
public class AjaxAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
// Custom code
}
}
When I debug the application, the method onAuthenticationFailure is called from SimpleUrlAuthenticationFailureHandler, but not in my custom AjaxAuthenticationFailureHandler even though I extend the same SimpleUrlAuthenticationFailureHandler.
What could be the mistake or any missed configuration to resolve the problem?
In this case, when we set the custom AjaxAuthenticationFailureHandler and then we configure failureUrl("/signin"),the configuration failureUrl("/signin") will overwrite the already configured AjaxAuthenticationFailureHandler with a new SimpleUrlAuthenticationFailureHandler.
This is the implementation of failureUrl and failureHandler methods as in AbstractAuthenticationFilterConfigurer.
public abstract class AbstractAuthenticationFilterConfigurer<B extends HttpSecurityBuilder<B>, T extends AbstractAuthenticationFilterConfigurer<B, T, F>, F extends AbstractAuthenticationProcessingFilter> extends AbstractHttpConfigurer<T, B> {
...........
private AuthenticationFailureHandler failureHandler;
private String failureUrl;
..........
public final T failureUrl(String authenticationFailureUrl) {
T result = failureHandler(new SimpleUrlAuthenticationFailureHandler(authenticationFailureUrl));
this.failureUrl = authenticationFailureUrl;
return result;
}
public final T failureHandler(AuthenticationFailureHandler authenticationFailureHandler) {
this.failureUrl = null;
this.failureHandler = authenticationFailureHandler;
return getSelf();
}
}
If we set only the failurehandler as below, our custom AjaxAuthenticationFailureHandler will be set.
.formLogin().loginPage("/signin")
.permitAll()
.failureHandler(ajaxAuthenticationFailureHandler)
.defaultSuccessUrl("/search").and()
If we want to set the failure URL we can set it in the custom AjaxAuthenticationFailureHandler using setDefaultFailureUrl(String defaultFailureUrl) which is derived from it's parent class SimpleUrlAuthenticationFailureHandler.
You can create a constructor in your AjaxAuthenticationFailureHandler class and pass in the defaultFailureUrl parameter down to the parent clss you extend (SimpleUrlAuthenticationFailureHandler). Your AjaxAuthenticationFailureHandler class would look like: -
#Component
public class AjaxAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
public AjaxAuthenticationFailureHandler (String defaultFailureUrl) {
super(defaultFailureUrl);
}
#Override
public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
// Custom code
}
}
Your configure block would then look like this: -
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/resources/**").permitAll()
.anyRequest().authenticated().and()
.formLogin()
.loginPage("/signin")
.failureHandler(new AjaxAuthenticationFailureHandler("/signin?auth=failure") // or whatever is a sensible url
.permitAll()
.defaultSuccessUrl("/search").and()
...
Note, it's really important that the "/signin?auth=failure" is added to an authorizeRequests() section otherwise the controller won't pick up the auth parameter e.g.
.and()
.authorizeRequests()
.antMatchers(
"/css/**",
"/js/**",
"/images/**",
"/signin**" // REALLY IMPORTANT !!!
).permitAll()
See https://stackoverflow.com/a/39618113/1692179 for more information on that.
Your controller can now check for the auth parameter e.g.
#Controller
public class SigninController
#RequestMapping(value="/login", method = RequestMethod.GET)
public String handleError (Model model,
#RequestParam(name = "auth", required = false) String auth) {
if ("error".equals(auth)) {
model.addAttribute("error", "invalid username/password");
}
return "login";
}
}
Hope this helps! :-)
In spring security 4 ,concurrent session not redirecting to expired url,instead it redirects to failure authentication url.
Following is the java configuration code snippet.
/*start of code*/
public class SecurityContextConfig extends WebSecurityConfigurerAdapter {
private static final Logger logger = LoggerFactory.getLogger(SecurityContextConfig.class);
/**
* #param auth
* #throws Exception
*/
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
logger.debug("configureGlobal() : Start : auth={}", auth);
auth.authenticationProvider(userDetailsAuthenticationProvider());
}
#Override
public void configure(WebSecurity web) throws Exception {
logger.debug("configure() : Start : web={}", web);
// This is here to ensure that the static content (JavaScript, CSS, etc)
// is accessible from the login page without authentication
web.ignoring().antMatchers("/resources/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
logger.debug("configure() : Start : http={}", http);
http
.authorizeRequests()
.antMatchers("/resources/**")
.permitAll()
.antMatchers("/login/**")
.permitAll()
.antMatchers("/authenticate/**")
.permitAll()
.antMatchers("/ssoLogout")
.permitAll()
.antMatchers("/forgotpassword/json")
.permitAll()
.antMatchers("/favicon.ico")
.permitAll()
.antMatchers("/secure/**")
.authenticated()
.and()
// This is where we configure our login form.
// login-page: the page that contains the login screen
// login-processing-url: this is the URL to which the login form
// should be submitted
// default-target-url: the URL to which the user will be
// redirected if they login successfully
// authentication-failure-url: the URL to which the user will be
// redirected if they fail login
// username-parameter: the name of the request parameter which
// contains the username
// password-parameter: the name of the request parameter which
// contains the password
.formLogin()
.loginPage("/")
.loginProcessingUrl("/authenticate")
.failureUrl("/")
.successHandler(loginSuccessHandler())
.and()
// This is where the logout page and process is configured. The
// logout-url is the URL to send
// the user to in order to logout, the logout-success-url is
// where they are taken if the logout
// is successful, and the delete-cookies and invalidate-session
// make sure that we clean up after logout
.logout()
.logoutUrl("/logout")
.logoutSuccessHandler(logoutHandler())
.deleteCookies("JSESSIONID")
.invalidateHttpSession(true)
.and()
.csrf()
.and()
// The session management is used to ensure the user only has
// one session. This isn't
// compulsory but can add some extra security to your
// application.
.sessionManagement()
//.invalidSessionUrl("/login")
.sessionFixation()
.changeSessionId()
.maximumSessions(1)
.expiredUrl("/login?reason=CONCURRENT_SESSION");
http.exceptionHandling().accessDeniedHandler(accessDeniedHandler());
logger.debug("configure() : End : http={}", http);
}
/**
* #return
*/
#Bean(name = "loginSuccessHandler")
public LoginSuccessHandler loginSuccessHandler() {
logger.debug("loginSuccessHandler() : Start.");
LoginSuccessHandler loginSuccessHandler = new LoginSuccessHandler();
logger.debug("loginSuccessHandler() : End : loginSuccessHandler={}", loginSuccessHandler);
return loginSuccessHandler;}
/**
* #return
*/
#Bean(name = "logoutHandler")
public LogoutHandler logoutHandler() {
logger.debug("logoutHandler() : Start.");
LogoutHandler logoutHandler = new LogoutHandler();
logger.debug("logoutHandler() : End : logoutHandler={}", logoutHandler);
return logoutHandler;
}
/**
* #return
*/
#Bean(name = "authenticationProvider")
public UserDetailsAuthenticationProvider userDetailsAuthenticationProvider() {
logger.debug("userDetailsAuthenticationProvider() : Start.");
UserDetailsAuthenticationProvider authenticationProvider = new UserDetailsAuthenticationProvider();
logger.debug("userDetailsAuthenticationProvider() : End : authenticationProvider={}", authenticationProvider);
return authenticationProvider;
}
#Bean(name="accessDeniedHandler")
public AccessDeniedHandlerImpl accessDeniedHandler(){
AccessDeniedHandlerImpl accessDeniedHandler=new AccessDeniedHandlerImpl();
accessDeniedHandler.setErrorPage("/login?reason=Access Denied");
return accessDeniedHandler;
}}
The behavior of expired url is not consistent.Sometimes working but sometimes not working .
What can be the issue?
The problem is that when it redirects to the expired URL, your user does not have access to the URL so it sends the user to the log in page (which is the same as the log in failure URL).
You need to ensure you grant access to every user to the expired URL. For example:
http
.authorizeRequests()
.antMatchers("/login")
.permitAll()
...
I am developing a project using spring boot with spring security, below is my security configuration
#Configuration
#EnableWebMvcSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private DataSource datasource;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable();
http
.authorizeRequests()
.antMatchers("/ideate","/homecreate").permitAll()
.anyRequest().authenticated();
http
.authorizeRequests().anyRequest().authenticated();
http
.formLogin().failureUrl("/login?error")
.defaultSuccessUrl("/")
.loginPage("/login")
.permitAll()
.and()
.logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/login")
.permitAll();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
JdbcUserDetailsManager userDetailsService = new JdbcUserDetailsManager();
userDetailsService.setDataSource(datasource);
PasswordEncoder encoder = new BCryptPasswordEncoder();
auth.userDetailsService(userDetailsService).passwordEncoder(encoder);
auth.jdbcAuthentication().dataSource(datasource);
}
}
After successful Log in of the user, i am calling a request mapping "/" written in the controller, the method will return a page which should contain username as model attribute how do i get the current logged in username?
Use the below code in your jsp ...
<%# taglib prefix="sec" uri="http://www.springframework.org/security/tags"%>
<sec:authentication property="principal.username" />
This bit of code helped me to fetch the user information to the jsp.