Spring boot security using userDetailsService - spring-boot

#Service
public class UserDetService implements UserDetailsService{
#Autowired
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
User user = userRepository.findByUserName(userName);
if(user == null){
throw new UsernameNotFoundException(userName);
}
return new UserPrincipal(user);
}
}
SecConfig.java
#Configuration
#EnableWebSecurity
public class SecConfig extends WebSecurityConfigurerAdapter{
#Bean
public UserDetailsService userDetailsService(){
return new UserDetService();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
auth.userDetailsService(userDetailsService());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/hello").access("hasRole('ROLE_ADMIN')")
.anyRequest().permitAll()
.and()
.formLogin().loginPage("/login")
.usernameParameter("username").passwordParameter("password")
.and()
.logout().logoutSuccessUrl("/login?logout")
.and()
.exceptionHandling().accessDeniedPage("/403")
.and()
.csrf();
}
}
I was learning spring security and didn't find anyone to ask if I was correct and is this all to do to secure login authentication or have I missed something?
Also I got a confusion that I haven't written any queries to get username and password, how does the validation of username and password from user input and database work and where the validation occur ?
Thank you for such a helpful people around here

You don't need to write queries because if you are using jpa it will write the queries for you.
This line: User user = userRepository.findByUserName(userName);
Will use the repository to auto-generate the query to check if the supplied parameter returns anything and in your service
if(user == null){
throw new UsernameNotFoundException(userName);
}
will throw the error if the repository couldn't find the user.
Hope this answers your question.

Related

Solution for dual security in spring boot application - OAuth2 (jwt token bearer) + X509 (certificates)

I tried to create a spring boot configuration with dual security checks on requests (Oauth2 token bearer and X509 certificates). I had 2 alternative ideas in mind, but cannot make it work either
dedicated endpoints for each type of security validation (/certif
for certification validation, /token for token validation)
all endpoints checked with either token or certificate validation
anything successfully would apply
This is my configuration that tries to achieve idea no 1:
#EnableResourceServer
#Configuration
#Order(1)
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Value("${xxx.auth.resourceId}")
private String resourceId;
#Autowired
private DefaultTokenServices tokenServices;
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(resourceId)
.tokenServices(tokenServices)
.tokenExtractor(new BearerTokenExtractor());
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.requestMatchers()
.antMatchers("/unsecured/**")
.antMatchers("/token/**")
.and().authorizeRequests()
.antMatchers("/unsecured/**").permitAll()
.anyRequest().authenticated()
;
}
}
#EnableResourceServer
#Configuration
#Order(2)
public class X509ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.requestMatchers()
.antMatchers("/certif/**")
.and()
.authorizeRequests()
.antMatchers("/certif/**").hasAuthority("AUTH")
.and().x509().subjectPrincipalRegex("CN=(.*?)(?:,|$)").userDetailsService(userDetailsService());
}
#Bean
public UserDetailsService userDetailsService() {
return new UserDetailsService() {
#Override
public UserDetails loadUserByUsername(String username) {
if (username.startsWith("xxx") || username.startsWith("XXX")) {
return new User(username, "",
AuthorityUtils
.commaSeparatedStringToAuthorityList("AUTH"));
}
throw new UsernameNotFoundException("User not found!");
}
};
}
}
For some reason I cannot make it work because filter OAuth2AuthenticationProcessingFilter seems to be deleting the authorization token created by filter X509AuthenticationFilter when I make a call with a certificate to /certif/info. I must mention that ResourceServerConfiguration is working ok when used alone and the /token/info endpoint is called with a token.
Mentioned filters are in spring-security-oauth:2.3.8 & spring-security-web:5.6.2
Orders have been changed in every direction but they seem to have no effect on how the filters are applied.
Any idea what is going on and how can I avoid this problem in order to achieve the desired behaviour?
You can try to config just one Configuration class.
You can join the two methods named as "configure", in just one.
I didn't test this code, but tell me if you did it working.
#Override
public void configure(HttpSecurity http) throws Exception {
http.requestMatchers()
.antMatchers("/unsecured/**")
.antMatchers("/token/**")
.antMatchers("/certif/**")
.and().authorizeRequests()
.antMatchers("/unsecured/**").permitAll()
.anyRequest().authenticated()
.antMatchers("/certif/**").hasAuthority("AUTH")
.and().x509().subjectPrincipalRegex("CN=(.*?)(?:,|$)").userDetailsService(userDetailsService());
;
}

spring security - Home page setup for authorize and unauthorize user

i'm stuck in spring security configuration. can any one help me for better solution. i have 2 jsp page one for login user and other for simple user. in which login user have option for logout and while in other jsp have option for login and signup.
my secuirty configuration class
#Configuration
#EnableWebSecurity
public class SecureConfig extends WebSecurityConfigurerAdapter {
#Autowired
UserDetailsService userDetailsService;
#Value("${winni.auth.exit}")
private String authExit;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().requestMatchers()
.antMatchers("/login","/web/**" ,"/exit","/action/**","/cart/**","/cart/xhr/**","/buyer/**")
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().permitAll().and()
.logout().logoutSuccessUrl(authExit);
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/assets/**","/account/**","/start/**");
}
}
home controller is
#RequestMapping("/")
public String sayHello(Principal principal) {
if (principal != null) {
return "login_user";
} else {
return "simple_user";
}
}
in every case Principal Object also null. how can solve this issue.

Getting redirected after login but I am not actually logged in Spring Security

Update: I tried implemting a CSRF token, and now when I try to log in, I always get redirected to the default Spring login page localhost:8080/login, and when I log in there again, it again redirects me to the login successful url....
This is my WebSecurityConfig
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
AccountDetailsService accountDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(accountDetailsService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception{
http
.authorizeRequests()
.antMatchers("/api/me").permitAll()
.antMatchers( HttpMethod.POST,"/api/addthing", "/api/addotherthing").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin()
.defaultSuccessUrl("http://192.168.1.105:3000/api/adminpage", true)
.and()
.logout()
.logoutSuccessUrl("http://192.168.1.105:3000/")
.logoutUrl("/logout")
.deleteCookies("JSESSIONID");
http.csrf().disable();
}
#Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
Here's my AccountDetailsService
#Service("userDetailsService")
#Transactional
public class AccountDetailsService implements UserDetailsService {
#Autowired
AccountService accountService;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Account account = accountService.findAccountByUsername(username);
return new org.springframework.security.core.userdetails.User(
account.getUsername(), account.getPassword(),
true, true, true, true,
accountService.getAuthorities(account)
);
}
}
Also the AccountSrvice:
#Service
public class AccountService {
#Autowired
AccountRepository accountRepository;
#Autowired
PasswordEncoder passwordEncoder;
public Account findAccountByUsername(String username){
return accountRepository.findFirstByUsername(username);
}
public List<GrantedAuthority> getAuthorities(Account account){
List<GrantedAuthority> roles = new ArrayList<>();
String role = account.getRole();
roles.add(new SimpleGrantedAuthority(role));
return roles;
}
public void createNewAccount(String username, String password, String role) {
Account account = new Account(username, passwordEncoder.encode(password), role);
accountRepository.save(account);
}
}
The problem that I am having is, after logging in, I get redirected to the /api/adminpage/ site, which means that the login is successful, right? But when I try getting my Principal with this method:
#CrossOrigin
#GetMapping("/api/me")
public Principal getMe(Principal principal){
return principal;
}
It just gives me an empty response, which means that I am not logged in.. Also when I try making a POST request to some of the urls in antMatches I can't... Can someone explain what am I doing wrong here?
Well, my whole day is now gone on this stupid little thing, but I got it working, and I'm not even happy about it anymore
The fix was to add "proxy": "http://192.168.1.105:8080" in my package.json in the React app.

Connect multiple authentication mechanisms Spring Boot Security

I have a security configuration for my application that authenticates the user via LDAP. This works out pretty fine, but now I'd like to add another AuthenticationProvider that does some more checks on the user that tries authenticate. So I tried to add a DbAuthenticationProvider that (for testing purposes) always denies the access. So when I am trying to log in with my domain account (that works for the activeDirectoryLdapAuthenticationProvider) I am not able to access the page because the second provider fails the authentication.
To accomplish this goal, I used the following code:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Value("${ad.domain}")
private String AD_DOMAIN;
#Value("${ad.url}")
private String AD_URL;
#Autowired
UserRoleComponent userRoleComponent;
#Autowired
DbAuthenticationProvider dbAuthenticationProvider;
private final Logger logger = LoggerFactory.getLogger(WebSecurityConfig.class);
#Override
protected void configure(HttpSecurity http) throws Exception {
this.logger.info("Verify logging level");
http.authorizeRequests().anyRequest().fullyAuthenticated().and().formLogin()
.successHandler(new CustomAuthenticationSuccessHandler()).and().httpBasic().and().logout()
.logoutUrl("/logout").invalidateHttpSession(true).deleteCookies("JSESSIONID");
http.formLogin().defaultSuccessUrl("/", true);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(activeDirectoryLdapAuthenticationProvider());
auth.authenticationProvider(dbAuthenticationProvider);
}
#Bean
public AuthenticationManager authenticationManager() {
return new ProviderManager(Arrays.asList(activeDirectoryLdapAuthenticationProvider(), dbAuthenticationProvider));
}
#Bean
public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(AD_DOMAIN,
AD_URL);
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
return provider;
}
}
And this is my DbAuthenticationProvider:
#Component
public class DbAuthenticationProvider implements AuthenticationProvider {
Logger logger = LoggerFactory.getLogger(DbAuthenticationProvider.class);
#Override
public Authentication authenticate(Authentication auth) throws AuthenticationException {
auth.setAuthenticated(false);
this.logger.info("Got initialized");
return auth;
}
#Override
public boolean supports(Class<?> authentication) {
return true;
}
}
Sadly I am able to log in (the access is not denied as I expected it to be). Did I miss out something?
Spring Won't use more than one AuthenticationProvider to authenticate the request, so the first (in the ArrayList) AuthenticationProvider that support the Authentication object and successfully authenticate the request will be the only one used. in your case it's activeDirectoryLdapAuthenticationProvider.
instead of using ActiveDirectoryLdapAuthenticationProvider, you can use a custom AuthenticationProvider that delegates to LDAP and do additional checks:
CustomerAuthenticationProvider implements AuthenticationProvider{
privtae ActiveDirectoryLdapAuthenticationProvider delegate; // add additional methods to initialize delegate during your configuration
#Override
public Authentication authenticate(Authentication auth) throws
AuthenticationException {
Authentication authentication= delegate.authenticate(auth);
additionalChecks(authentication);
return auth;
}
#Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
public void additionalCheck(Authentication authentication){
// throw AuthenticationException when it's not allowed
}
}
That is not how an AuthenticationProvider works, only one will be consulted for authentication. Apparently you want to combine some information from LDAP and from the DB. For this you can configure a custom UserDetailsContextMapper and/or GrantedAuthoritiesMapper. The default implementation will use the information from LDAP to contruct the UserDetails and its GrantedAuthorities however you could implement a strategy which consults the database.
Another solution is to use the LdapUserDetailsService which allows you to use the regular DaoAuthenticationProvider. The name is misleading as it actually requires an UserDetailsService. This AuthenticationProvider does additional checks using the UserDetailsChecker, which by default checks some of the properties on the UserDetails, but can be extended with your additional checks.
NOTE: The LdapUserDetailsService uses plain LDAP so I don't know if that is applicable to the slightly different Active Directory approach!
A final solution could be to create a DelegatingAuthenticationProvider which extends from AbstractUserDetailsAuthenticationProvider so that you can reuse the logic in there to utilize the UserDetailsChecker. The retrieveUser method would then delegate to the actual ActiveDirectoryLdapAuthenticationProvider to do the authentication.
NOTE: Instead of extending the AbstractUserDetailsAuthenticationProvider you could of course also create a simpler version yourself.
All in all I suspect that creating a customized UserDetailsContextMapper would be the easiest and when not found in DB throw an UsernameNotFoundException. This way the normal flow still applies and you can reuse most of the existing infrastructure.
As sample work around on multiple authentication mechanism :
find the code
#Configuration
#EnableWebSecurity
#Profile("container")
public class CustomWebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AuthenticationProvider authenticationProvider;
#Autowired
private AuthenticationProvider authenticationProviderDB;
#Override
#Order(1)
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
}
#Order(2)
protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProviderDB);
}
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/scripts/**","/styles/**","/images/**","/error/**");
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/rest/**").authenticated()
.antMatchers("/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.successHandler(new AuthenticationSuccessHandler() {
#Override
public void onAuthenticationSuccess(
HttpServletRequest request,
HttpServletResponse response,
Authentication a) throws IOException, ServletException {
//To change body of generated methods,
response.setStatus(HttpServletResponse.SC_OK);
}
})
.failureHandler(new AuthenticationFailureHandler() {
#Override
public void onAuthenticationFailure(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException ae) throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}
})
.loginProcessingUrl("/access/login")
.and()
.logout()
.logoutUrl("/access/logout")
.logoutSuccessHandler(new LogoutSuccessHandler() {
#Override
public void onLogoutSuccess(
HttpServletRequest request,
HttpServletResponse response,
Authentication a) throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
}
})
.invalidateHttpSession(true)
.and()
.exceptionHandling()
.authenticationEntryPoint(new Http403ForbiddenEntryPoint())
.and()
.csrf()//Disabled CSRF protection
.disable();
}
}
configured two authentication providers in Spring Security
<security:authentication-manager>
<security:authentication-provider ref="AuthenticationProvider " />
<security:authentication-provider ref="dbAuthenticationProvider" />
</security:authentication-manager>
configuration which helps configure multiple authentication providers in java config.
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
auth.authenticationProvider(DBauthenticationProvider);
}
#Configuration
#EnableWebSecurity
public class XSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private LDAPAuthenticationProvider authenticationProvider;
#Autowired
private DBAuthenticationProvider dbauthenticationProvider;
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/scripts/**","/styles/**","/images/**","/error/**");
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
auth.authenticationProvider(dbauthenticationProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/","/logout").permitAll()
.antMatchers("/admin").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/index")
.loginProcessingUrl("/perform_login")
.usernameParameter("user")
.passwordParameter("password")
.failureUrl("/index?failed=true")
.defaultSuccessUrl("/test",true)
.permitAll()
.and()
.logout().logoutUrl("/logout")
.logoutSuccessUrl("/index?logout=true").permitAll()
.and()
.exceptionHandling().accessDeniedPage("/error");
}
}
objectPostProcessor inside the configure method need AuthenticationManagerBuilder to actually build the object before we can access and change the order of the providers
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource)
.passwordEncoder(new BCryptPasswordEncoder());
auth.authenticationProvider(new CustomAuthenticationProvider(this.dataSource));
auth.objectPostProcessor(new ObjectPostProcessor<Object>() {
#Override
public <O> O postProcess(O object) {
ProviderManager providerManager = (ProviderManager) object;
Collections.swap(providerManager.getProviders(), 0, 1);
return object;
}
});
}

ProviderManager.authenticate called twice for BadCredentialsException

Spring 4.1 and Spring Security 3.2:
We implemented a Custom Authentication Provider, that throws a BadCredentialsException if user enters an incorrect password.
When the BadCredentialsException is thrown, the ProviderManager.authenticate method is called, which calls the authenticate method in the Custom Authentication again. When a LockedException is thrown, the authenicate method in the Custom Authentication Provider is not called again. We are planning on keeping a count of number of login attempts, so we don't want the authenticate method called twice. Does anyone know why the authenticate method in the custom authentication class would be called twice?
WebConfig:
#Configuration
#EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomAuthenticationProvider customAuthenticationProvider;
#Autowired
private AMCiUserDetailsService userDetailsService;
#Autowired
private CustomImpersonateFailureHandler impersonateFailureHandler;
#Autowired
private LoginFailureHandler loginFailureHandler;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/jsp/*.css","/jsp/*.js","/images/**").permitAll()
.antMatchers("/login/impersonate*").access("hasRole('ADMIN') or hasRole('ROLE_PREVIOUS_ADMINISTRATOR')")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.jsp")
.defaultSuccessUrl("/jsp/Home.jsp",true)
.loginProcessingUrl("/login.jsp")
.failureHandler(loginFailureHandler)
.permitAll()
.and()
.logout()
.logoutSuccessUrl("/login.jsp?msg=1")
.permitAll()
.and()
.addFilter(switchUserFilter())
.authenticationProvider(customAuthenticationProvider);
http.exceptionHandling().accessDeniedPage("/jsp/SecurityViolation.jsp"); //if user not authorized to a page, automatically forward them to this page.
http.headers().addHeaderWriter(new XFrameOptionsHeaderWriter(XFrameOptionsMode.SAMEORIGIN));
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthenticationProvider);
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
//Used for the impersonate functionality
#Bean CustomSwitchUserFilter switchUserFilter() {
CustomSwitchUserFilter filter = new CustomSwitchUserFilter();
filter.setUserDetailsService(userDetailsService);
filter.setTargetUrl("/jsp/Impersonate.jsp?msg=0");
filter.setSwitchUserUrl("/login/impersonate");
filter.setExitUserUrl("/logout/impersonate");
filter.setFailureHandler(impersonateFailureHandler);
return filter;
}
}
Custom Authentication Provider:
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Autowired(required = true)
private HttpServletRequest request;
#Autowired
private AMCiUserDetailsService userService;
#Autowired
private PasswordEncoder encoder;
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName().trim();
String password = ((String) authentication.getCredentials()).trim();
if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
throw new BadCredentialsException("Login failed! Please try again.");
}
UserDetails user;
try {
user = userService.loadUserByUsername(username);
//log successful attempt
auditLoginBean.setComment("Login Successful");
auditLoginBean.insert();
} catch (Exception e) {
try {
//log unsuccessful attempt
auditLoginBean.setComment("Login Unsuccessful");
auditLoginBean.insert();
} catch (Exception e1) {
// TODO Auto-generated catch block
}
throw new BadCredentialsException("Please enter a valid username and password.");
}
if (!encoder.matches(password, user.getPassword().trim())) {
throw new BadCredentialsException("Please enter a valid username and password.");
}
if (!user.isEnabled()) {
throw new DisabledException("Please enter a valid username and password.");
}
if (!user.isAccountNonLocked()) {
throw new LockedException("Account locked. ");
}
Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
List<GrantedAuthority> permlist = new ArrayList<GrantedAuthority>(authorities);
return new UsernamePasswordAuthenticationToken(user, password, permlist);
}
public boolean supports(Class<? extends Object> authentication) {
return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}
The reason is that you add your authentication provider twice, one time in configure(HttpSecurity) and one time in configure(AuthenticationManagerBuilder). This will create a ProviderManager with two items, both being your provider.
When authentication is processed, the providers will be asked in order until a success is made, unless a LockedException or similar status exception is thrown, then the loop will break.
There may be a situtation which you don't override configure(AuthenticationManagerBuilder) and still same AuthenticationProver's authenticate method gets called twice like Phil mentioned in his comment in the accepted answer.
Why is that?
The reason is that when you don't override configure(AuthenticationManagerBuilder) and have an AuthenticationProvider bean, it will be registered by Spring Security, you don't have to do anything else.
However, when configure(AuthenticationManagerBuilder) overridden, Spring Security will invoke it and won't try to register any provider by itself.
If you're curious, you can take a look the related method. disableLocalConfigureAuthenticationBldr is true if you override configure(AuthenticationManagerBuilder).
So, briefly, if you want to register just one custom AuthenticationProvider then do not override configure(AuthenticationManagerBuilder), do not call authenticationProvider(AuthenticationProvider) in configure(HttpSecurity), just make your AuthenticationProviver implementation bean by annotating #Component and you're good to go.

Resources