Spring Security 4 enables developers to write less code, but sometimes this also causes thing easily to get out of control. For example, now I am writing a login function, once the user pressed a button (login/unionauth URI), an OAuth 2.0 like authentication offered by a 3rd party would be launched, and finally the result comes back and we compare the user with our local database. In order to do so, first I have adapter class like this:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/**")
.authorizeRequests()
.antMatchers("/", "/login**", "/webjars/**").permitAll()
.anyRequest().authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/"))
.and()
.logout()
.logoutSuccessUrl("/").permitAll()
.and()
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class);
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailsService);
}
Then I have ssoFilter() like this:
public class UnionAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter {
#Override
public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException {
// authentication steps.
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("tom", "N/A", null);
AuthenticationManager manager = this.getAuthenticationManager();
return manager.authenticate(token);
}
Now, the problem is, manager is null. Why it is null? I think in case of password username mode, the auth.userDetailsService(myUserDetailsService); in adapter would enable a DAO manager. In official website, it has this:
How to Add a Local User Database
Many applications need to hold data about their users locally, even if
authentication is delegated to an external provider. We don’t show the
code here, but it is easy to do in two steps.
Choose a backend for your database, and set up some repositories (e.g.
using Spring Data) for a custom User object that suits your needs and
can be populated, fully or partially, from the external
authentication.
Provision a User object for each unique user that logs in by
inspecting the repository in your /user endpoint. If there is already
a user with the identity of the current Principal, it can be updated,
otherwise created.
Hint: add a field in the User object to link to a unique identifier in
the external provider (not the user’s name, but something that is
unique to the account in the external provider).
Any idea how to add database into Spring Security OAuth2 or why the manager in the first paragraph is null?
Related
I have a question about storing "base" password for spring security app. I read documentation and IMHO i should have first pass stored somewhere hardcoded. Is that right or how i should be done?
As example i've post defauld helloWorld code from spring security.
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
#Bean
#Override
public UserDetailsService userDetailsService() {
PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
String s = encoder.encode("password");
UserDetails user = User.withUsername("userName")
.password(s)
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
}
You have several options.
Hardcode it.
Store username/pass in a .property file. This gives you more flexibility, and also an ability to disable the user (e.g. set the name to empty and skipping it in the code)
Generate password via existing PasswordGenerator and add login/pass manually to a DB via SQL. This way you have even more flexibility, you can have as many initial users as you want (and you can always delete them), and also this approach can guarantee that logins will be unique (you need some special handling in your code to check, that the login of any new user doesn't match the login of the first user).
I'm working on a Web Project with different Spring Boot WebMVC Clients. Some of this Clients needs a authorization and I solved it with a Spring Security OAuth2 Server. The authentication works fine and I had no problems. Some Clients didn't need an login and they are public for all.
Technical facts: All clients use a mix between Angular, jQuery and simple JSP's. All apps use Spring Security and the public app configuration is like this:
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.antMatcher("/**")
.authorizeRequests()
.antMatchers("/**").permitAll()
.antMatchers("/fonts/**").permitAll()
.anyRequest().authenticated();
}
Now my question: I plan to build a Login (or Logout) Button in the Header of all apps. In the apps with a required authentication is that no problem. I can check the principal is null or not. But how can I solve this in public apps. The principal is ever null and the client didn't check the authentication status with the server. I had some ideas to fix it but nothing is working. The best way would be a automatic check in Spring Boot. But how can I configure this? Maybe I can check it with JavaScript, but my shots also didn't work.
Maybe it would help - two of my apps:
https://www.planyourtrip.travel (public application)
https://profile.planyourtrip.travel (memberonly application)
UPDATE: Maybe a better example
If I configure a public app like this
#Configuration
#EnableOAuth2Sso
public static class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.antMatcher("/**")
.authorizeRequests()
.antMatchers("/**").permitAll()
.anyRequest().authenticated();
}
}
and the MVC Controller like this
#RequestMapping("/{([a-z]{2})}")
public ModelAndView start(final Principal principal) {
return new ModelAndView("start");
}
then is the Principal ever null. I think that is my Problem. I need a check with the OAuth Server and if i logged in is the principal set and if I'm not logged in it should be null.
If I had understood your question correctly, than you need that some URL pattern can be accessed without authentication. Than in that case you can use the following method to prevent authentication for certain URL patterns -
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/urlPattern");
}
permitAll() method defines that all the authenticated users can access mentioned URL pattern. So if you want some users to access some resources (URL) without authentication, than you have to use above method.
I am trying to integrate LDAP authentication for my Spring MVC app. The users are successfully able to log in, if I set the contextSource to a dummy user DN and respective password.
What I want to do is to be able to bind the ldap connection without using the dummy user.
Here's the code of what works -
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/css/**").permitAll()
.antMatchers("/","/login**").permitAll()
.antMatchers("/home_vm","/details/**").authenticated()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.defaultSuccessUrl("/home_vm", true)
.and()
.logout()
.permitAll();
http.headers().httpStrictTransportSecurity();
}
#Configuration
#Profile({"default", "opt_ad_auth"})
protected static class ActiveDirectoryAuthenticationConfiguration extends
GlobalAuthenticationConfigurerAdapter {
#Value("${app.ldap.url}")
private String ldapURL;
#Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource(ldapURL);
contextSource.setUserDn("cn=Dummy User,cn=Users,dc=somecompany,dc=com");
contextSource.setPassword("mypassword");
contextSource.setReferral("follow");
contextSource.afterPropertiesSet();
auth.ldapAuthentication()
.userSearchFilter("(sAMAccountName={0})")
.contextSource(contextSource)
;
}
}
}
Now I have tried to remove the hardcoded userDn and password (updated init())-
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication()
.userSearchFilter("(sAMAccountName={0})")
.contextSource()
.url(ldapURL)
;
}
}
The app starts fine, but I get exception - "a successful bind must be completed on the connection".
Stacktrace -
org.springframework.security.authentication.InternalAuthenticationServiceException: Uncategorized exception occured during LDAP processing; nested exception is javax.naming.NamingException: [LDAP: error code 1 - 000004DC: LdapErr: DSID-0C0906E8, comment: In order to perform this operation a successful bind must be completed on the connection., data 0, v1db1
[UPDATE] I have modified the init method to the following to more closely follow the spring tutorial(https://spring.io/guides/gs/authenticating-ldap/) -
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication()
.userDnPatterns("sAMAccountName={0}")
.contextSource().url(ldapURL)
;
}
I don't get the before-mentioned bind exception, but still not able to authenticate. Bad credentials.
You need to do this in two steps:
Bind to LDAP as an administrative user that has enough privileges to search the tree and find the user via whatever unique information you have about him.
Rebind to LDAP as the found user's DN using the supplied password.
Disconnect.
If all that succeeds, the username existed and the password was correct. If there was any failure you should treat it as a loging failure. Specifically, you should not tell the user whether it was the username not being found or the password being incorrect that was the reason for the failure: this is an information leak to an attacer.
You can authenticate with LDAP in 2 forms:
You can make a bind with the complete DN of the user (full path in the LDAP tree) and the password.
Or you can bind to LDAP as a superuser that can search on all the LDAP tree. The LDAP authenticator finds the user DN and compare the user password with the password in the user entry.
In your first code you use the second system. You need a user that can bind on the LDAP and find the user entry.
If you want user the second system, you must supply the complete DN of the user entry not the uid only.
I am trying to configure spring boot-Embedded Tomcat basic HTTP authentication with multiple roles, with most of the url's similar but few of them specific to each role. Here for first role the basic HTTP authentication pops up and working fine. With below code,
#Configuration
#EnableWebMvcSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class TestSecurityAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests().antMatchers(null, getAppAdminRolePaths()).authenticated()
.anyRequest().hasAnyRole("APPADMIN")
.and()
.httpBasic();
http.csrf().disable()
.authorizeRequests().antMatchers(null, getAppUserRolePaths()).authenticated()
.anyRequest().hasAnyRole("APPUSER")
.and()
.httpBasic();
http.authorizeRequests().antMatchers(null, new String[]{"/app/appOwnerView.html"}).authenticated()
.anyRequest().hasAnyRole("APPOWNER")
.and()
.httpBasic();
}
#Override
#Autowired
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("appadminname").password("appadminpwd").roles("APPADMIN").and()
.withUser("appusername").password("appuserpwd").roles("APPUSER").and()
.withUser("appownername").password("appoownerpwd").roles("APPOWNER");
}
private static String[] getAppAdminRolePaths(){
return new String[]{"/appweb/*",
"/app/checkService.html",
"/app/index.html",
"/app/testData.html",
"/app/adminView.html",
"/app/demo.html"};
}
private static String[] getAppUserRolePaths(){
return new String[]{"/appweb/*",
"/app/checkService.html",
"/app/index.html",
"/app/testData.html",
"/app/userView.html",
"/app/demo.html"};
}
}
For HTTP username/password popup in browser with url http://localhost:8080/app/index.html say with appadminname/appadminpwd it works fine. But for same url if I enter appusername/appuserpwd it throws HTTP 403 Forbidden access error. Here why is the second role APPUSER configured is throwing this error is I am not sure. Please let know if some way to get this resolved.
Thanks
I appreciate this question is a little old now, but this may still be useful to someone.
Firstly, I'm not sure why your calls to antMatchers() supply null as the first argument; antMatchers() expects a list of strings defining the URLs to be covered by this rule, so I'm not sure what null is expected to match in this case.
Secondly, anyRequest() means that this rule will be applied to any request made to the application regardless of the URL used, and Spring will apply security rules in the order that they are defined. You would typically define URLs and their associated roles first, and then default to a rule for any other request that must be authenticated (but does not necessarily need any specific roles) with something like anyRequest().authenticated()
Your first rule says that any request made to the application must be made by users with the role APPADMIN, which denies you access when you try to log in as appusername, so the second rule to allow APPUSERs is not even processed.
Thirdly, you are making multiple calls to http.authorizeRequests() when you should probably actually be chaining them together, for example:
http.csrf().disable().authorizeRequests()
.antMatchers( getAppAdminRolePaths() ).hasRole("APPADMIN")
.antMatchers( getAppUserRolePaths() ).hasRole("APPUSER")
.anyRequest().authenticated();
Lastly, when you have just a single role to check against, you can use hasRole() instead of hasAnyRole().
You also don't need to supply authenticated() and hasRole() in the same rule because hasRole() implies that the user is already authenticated.
You can find more explanations and examples in the Spring documentation: http://docs.spring.io/spring-security/site/docs/4.0.3.RELEASE/reference/htmlsingle/#authorize-requests
I've got spring security configured as
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = false)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.jee()
.mappableRoles("ROLE1", "ROLE2");
}
}
And then #Secured annotations with roles on the rest endpoints.
Doesn't matter what I do I don't seem to be able to create a custom handler for authorization (i.e. a user logged in successfully but doesn't have the right role to access a particular endpoint) error events.
What I tried was:
An exception handler with #ExceptionHandler(value = AccessDeniedException.class) - doesn't get called. I understand that's by design, ok.
AuthenticationEntryPoint configured as
http.exceptionHandling().authenticationEntryPoint(new RestAuthenticationEntryPoint())
#Component( "restAuthenticationEntryPoint" )
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence( HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException ) throws IOException {
// logging
}
}
-doesn't get called
ApplicationListener - I can see it's getting called on context closed, so it's registered correctly but not called on authorization error.
All I need is a simple handler to log unsuccessful authorization events.
It completely slipped my mind that the allowed roles are listed in web.xml as well for j2ee container authentication to work. So any user without a least one of those roles was just being rejected by the container.
Otherwise the first, simplest, method works fine. Hopefully my mistake will help someone in the future