I am new to Java and I trying to write first web application with Spring framework (I have some experience with django/python and sinatra/ruby).
I need to implement user authentication system and have no idea what is the best way to do this correctly. Should I use Spring Security for this? Or there is some another ways?
Since you starting a new application, I strongly urge to use Spring Boot. It will take away a lot of your initial pain in configuring the various aspects of Spring.
As for security, you should go with Spring Security which of course is easy to configure with Spring Boot.
Following this guide, a simple Spring Security configuration which uses in memory authentication would look like:
#Configuration
#EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated();
http
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER");
}
}
As you can see, the first method is used to configure which parts of the application are to be secured and how, while the second method configures how the users will be authenticated.
One common way to provide a customized way to authenticate users, is to provide a custom UserDetailsService implementation as is done here
A very easy way to configure authentication when you already have configured a DataSource and the user credentials are stored there, is the following:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private DataSource dataSource;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated();
http
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
#Override
protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth
.jdbcAuthentication()
.dataSource(dataSource)
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN").and()
.withDefaultSchema();
}
}
Here is the complete reference of Spring Security. Also here is security part of the Spring Boot documentation. You can find a multitude of sample applications using various aspects of Spring Security here
Initially it may seem that Spring Security is complicated, but once you get the hang of it, you'll appreciate it's extended feature set and customizability.
One final note, when to comes to things like security which are common in so many applications, there is no need to reinvent the wheel! Go with Spring Security (or perhaps Apache Shiro)
Related
I have a very basic Spring Security setup using Session. My problem is that I can't find a way to use any kind of Session Listener (both Spring and Servlet API versions) to listen to SessionCreated event. Login is working and session is being created properly.
The reason I need a listener is because I want to initialize certain session attributes (ex. shopping kart, recent items list) so I can access them seamlessly from #Controller request mappings, without having to worry whether session attributes are initialized.
Security configuration code:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
DataSource dataSource;
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
.and()
.authorizeRequests()
.antMatchers("/secured/**").authenticated()
.anyRequest().permitAll()
.and()
.formLogin()
.loginPage("/login")
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/")
.and()
.rememberMe().key("unique");
}
...
}
First, I have tried the most basic session listenter:
#Component
public class InitHttpSessionListener implements HttpSessionListener {
public void sessionCreated(HttpSessionEvent event) {
...
}
}
I have also tried answers from here, which also didn't work
As what was getting clear from your comments is that you are using Spring Session JDBC. Due to the nature of JDBC this doesn't support publishing of session events and thus you cannot listen to those events.
As a workaround you could create your own AuthenticationSuccessHandler and put the logic for filling the Session in there. Or listen to an AuthenticationSuccessEvent using a Spring event listener (would be a bit harder to get to the session but doable).
The goal is to manipulate the roles of a user(or simply create a new Authentication object since I'v got a hunch they cant be manipulated directly) before its stored in the context. After a lot of digging I found that the authentication is perfomred in the OAuth2ClientAuthenticationProcessingFilter.attemptAuthentication and later stored using a sessionStrategy.
The part where I am stuck at is telling spring to use/override that filter.
So far I have managed to create a CustomOAuth2ClientAuthenticationProcessingFilter extending the original one.
I tried to instantiate the filter and add it to the filter chane like so.
#Configuration
#EnableOAuth2Sso
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/", "/login**", "/callback/**", "/resources/**", "/static/**", "/webjars/**", "/webjar/**", "/error**")
.permitAll()
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.csrf().disable()
.logout()
.permitAll()
.logoutSuccessUrl("/")
.and().addFilterAt(createCustomOAuth2ClientAuthenticationProcessingFilter(), AbstractPreAuthenticatedProcessingFilter.class)
;
}
private CustomOAuth2ClientAuthenticationProcessingFilter createCustomOAuth2ClientAuthenticationProcessingFilter(){
OAuth2SsoProperties sso = (OAuth2SsoProperties)this.getApplicationContext().getBean(OAuth2SsoProperties.class);
OAuth2RestOperations restTemplate = ((UserInfoRestTemplateFactory)this.getApplicationContext().getBean(UserInfoRestTemplateFactory.class)).getUserInfoRestTemplate();
ResourceServerTokenServices tokenServices = (ResourceServerTokenServices)this.getApplicationContext().getBean(ResourceServerTokenServices.class);
CustomOAuth2ClientAuthenticationProcessingFilter customFilter=new CustomOAuth2ClientAuthenticationProcessingFilter(sso.getLoginPath());
customFilter.setRestTemplate(restTemplate);
customFilter.setTokenServices(tokenServices);
customFilter.setApplicationEventPublisher(this.getApplicationContext());
return customFilter;
}
but that, as expected, just calls both filters- mine and the default one.
Is there a way to "replace" the original filter functionality without creating serious issues and save the ability to later use the #Secured anotation for example, in order to secure application endpoints ?
I'v read about using postProcessor in the configuration but never found an extensive well expleined answer.I'v also read about custom configuration classes but not for OAuth2 . Any suggestions are welcome.
Spring usually eagerly loading the spring security configuration while starting the application. I'm using OAuth with Spring Security
I'm maintaining a configuration table for storing the SSO related values (like jwk-url, client_id, client_secret). This values will be populated by an admin user via CRUD in the same spring boot application.
Then only the jwk-url is available to be configure in the Spring security configuration (refer below code - jwkSetUri(...)). This would not available at the application startup.
So I wanted to initialise the spring security configuration after the value is loaded into the table, like a lazy loading (#Lazy) at runtime. I know how to do Lazy loading of a regular class/service.
But still I'm not sure how to invoke the configure(HttpSecurity http) method at runtime and how to p
ass the HttpSecurity parameter. When I just try invoke new ResourceServerConfiguration() like a lazy loading at runtime, I don't see the configure() method is called. (Or) this class needs to be maintained as bean and lazy load whenever needed. But still not sure about how to call configure() in code.
Another thing is how to refresh/reload the spring security configuration at runtime, if the JWK url is changed by admin. Then only the spring security configuration can take effect of the changes.
#Configuration
#EnableWebSecurity
public class ResourceServerConfiguration extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.cors()
.and()
.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.oauth2ResourceServer()
.authenticationEntryPoint(oAuth2AuthenticationEntryPoint)
.accessDeniedHandler(oAuth2AccessDeniedHandler)
.jwt()
// Some Auth server URL which would be fetch from table
.jwkSetUri(ssoConfigService.getActiveSSOCertificateURL());
// Eg. http://localhost:8090/auth/realms/demo-app/protocol/openid-connect/certs
}
}
I have already referred these links. But it doesn't help for my purpose. Any help would be appreciated.
How do I lazy load Spring Security?
How to reload the Configure method of WebSecurityConfigurerAdapter when the application is up and running
Modify Spring Security Config at Runtime
Configure Spring HTTP Security at Runtime
Please, check this link Customizing CORS Filtering at Runtime that include a similar use case related to your but for him, he needed to change allowed origins dynamically. They decide to create a new filter and simple extends OncePerRequestFilter.
Take in account to check the OAuth2ResourceServerProperties for your use case.
UPDATING:
Try with this code for this scenario:
Another thing is how to refresh/reload the spring security configuration at runtime, if the JWK url is changed by admin. Then only the spring security configuration can take effect of the changes.
#Override
public void configure(HttpSecurity http) throws Exception {
http.cors()
.and()
.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.anyRequest().authenticated()
// TODO: test with and without this and check if work for you
.and()
.oauth2ResourceServer()
.authenticationEntryPoint(oAuth2AuthenticationEntryPoint)
.accessDeniedHandler(oAuth2AccessDeniedHandler)
.jwt()
// Some Auth server URL which would be fetch from table
.jwkSetUri(ssoConfigService.getActiveSSOCertificateURL());
// Eg. http://localhost:8090/auth/realms/demo-app/protocol/openid-connect/certs
http.addFilterBefore(new OncePerRequestFilter() {
// Every time a request occur, this method will be called.
#Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
try {
http.oauth2ResourceServer()
.authenticationEntryPoint(oAuth2AuthenticationEntryPoint)
.accessDeniedHandler(oAuth2AccessDeniedHandler)
.jwt()
// Some Auth server URL which would be fetch from table
.jwkSetUri(ssoConfigService.getActiveSSOCertificateURL());
} catch (Exception e) {
e.printStackTrace();
}
}
}, BasicAuthenticationFilter.class);
}
I hope this info can help you.
I am having an issue with Spring 5 form based authentication. This is only an example at this point as I am trying to isolate the issue I'm having. My plan is to include this configuration in a larger project with multiple configurations. The below code (which can be found anywhere) works as expected:
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
{
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
auth.inMemoryAuthentication()
.passwordEncoder(passwordEncoder)
.withUser("user").password(passwordEncoder.encode("password")).roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.and().csrf().disable();
}
}
and following code does not work after adding antmatcher:
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
{
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
auth.inMemoryAuthentication()
.passwordEncoder(passwordEncoder)
.withUser("user").password(passwordEncoder.encode("password")).roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception
{
http.antMatcher("/test/**")
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.and().csrf().disable();
}
}
Notice that I've only added an antmatcher to this config. I only want the config to apply to /test/**. And, in this case, I'm not doing anything custom with regards to the form login and would like to get Spring default form. Of course in a real application I would not use the default but this is only for example purposes. My first config displays the default internal Spring form correctly when I GET the following URL: http://localhost:8080/webapp/test/anything. And I can authenticate successfully. For the second configuration I get 404 errors when issuing the same URL.
Spring security does in fact try to redirect to: http://localhost:8080/webapp/login however, the url does not exist. I did try the following change:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/test/**").authorizeRequests().anyRequest().authenticated()
.and().formLogin().permitAll().loginPage("/test/login").loginProcessingUrl("/test/login")
.and().logout().logoutUrl("/test/logout").invalidateHttpSession(true).logoutSuccessUrl("/");
}
This did not work either. I made several attempts, but have not been successful. Is it possible that the default form behavior is disabled when an antmatcher is defined?
This is ideal solution for your requirement
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
.requestMatchers().antMatchers("/test/**", "/customLoginPage", "/logout").and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/customLoginPage") //If you don't configure default is "/login"
.usernameParameter("userName").passwordParameter("password") //In case of custom form parameters
.defaultSuccessUrl("/app/user/dashboard") //If you don't configure default is "/"
.failureUrl("/login?error=true") //If you don't configure default is "/login?error"
.and().csrf().disable();
}
Let me explain Some cases how spring security deals with
Case 1:
http.authorizeRequests()
is equals to
http.antMatcher("/**").authorizeRequests()
A proxy filter will be defined and url-pattern for that filter will be "/**". With this type of configuration there will be no problem as it is a wild card. But in some cases we don't want to define wild card "/**" then we should configure requestMatchers correctly otherwise we will end up in lot of unguessable problems.
Case 2:
http.antMatcher("/test/**").authorizeRequests()
Here proxy filter will be added with URL pattern "/test/**" and requests with /login and /logout can't pass through the added filter. To overcome this .requestMatchers() should be used as given below
http
.requestMatchers().antMatchers("/test/**", "/customLoginPage", "/logout")
.and().authorizeRequests()
This means filter with filter mapping as given below
<filter-mapping>
<filter-name>/test/**</filter-name>
<url-pattern>/customLoginPage</url-pattern>
<url-pattern>/logout</url-pattern>
</filter-mapping>
You can try out some of basic examples(Working) from my github repository
After many tries, the following is working for me. Only showing the configure method:
#Override
protected void configure(HttpSecurity http) throws Exception
{
http.antMatcher("/test/**")
.authorizeRequests()
.antMatchers("/test/login").permitAll()
.antMatchers("/test/logout").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.jsp").permitAll()
.loginProcessingUrl("/test/login").permitAll()
.failureUrl("/login.jsp?error=yes")
.and()
.logout().permitAll().logoutUrl("/test/logout").permitAll()
.and().csrf().disable();
}
I did need to create my own login form (login.jsp), however the processing of the login form (the POST) is handled by Spring default processing. Really, that was the important piece. Another key point to make is that the login form POST needs to point to /webapp/test/login. I suppose that the Spring configuration needs these additional "tips" in order to trigger the default form processing in cases where the antMatcher call must be present.
I had updated spring security from 3x to 4.0.1.RELEASE. Then I finally had the change to fully remove old XML, and replace it with pure java config. But my security isn't working properly.
Problem:
my default login page does not authorize, under POST /login.htm I have 404.
my main app can run as unauthorized
because I have 404 on login POST, I am not entering UserDetailsService
all beans are provided into this configuration, I have no context start problems
My configuration file:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
#ComponentScan(basePackages = {"com.dal.dao.security", "com.services.security"})
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
LocalUserDao userDao;
#Autowired
UserRoleMapper roleMapper;
#Autowired
StandardPasswordEncoder passwordEncoder;
private UserDetailsService methodSecurityService() throws Exception {
return new UserDetailsServiceImpl(userDao, roleMapper);
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(methodSecurityService()).passwordEncoder(passwordEncoder);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/*")
.authenticated()
.and()
.formLogin()
.loginPage("/login.htm").failureUrl("/login.htm?error")
.usernameParameter("username")
.passwordParameter("password")
.and().logout().logoutSuccessUrl("/index.htm")
.and().csrf()
.and().exceptionHandling().accessDeniedPage("/403");
}
}
Could anyone help me with this? I have already watched few no-xml configuration, but they don't seem to be working on my example.
Source of my code can be found here.
I think the fix is simple (i hope) you didn't advise that anyone can access your login form:
http.authorizeRequests()
.antMatchers("/*")
.authenticated()
.and()
.formLogin()
.loginPage("/login.htm").failureUrl("/login.htm?error")
.usernameParameter("username")
.passwordParameter("password")
.permitAll() // ADD THIS LINE
.and().logout().logoutSuccessUrl("/index.htm")
.and().csrf()
.and().exceptionHandling().accessDeniedPage("/403");
Essentially you are redirecting users to login page without enabling unauthenticated access to it. Basically you are asking them to authenticate to view the authentication form :).
From Spring Security:
Granting access to the formLogin() URLs is not done by default since
Spring Security needs to make certain assumptions about what is
allowed and what is not. To be secure, it is best to ensure granting
access to resources is explicit.