Getting a user's groups from LDAP in Spring Boot - spring-boot

Our legacy application is deployed on Glassfish, and uses javax.security to manage authorization.
The following code retrieves from LDAP the Active Director groups the user is a member of:
try{
subject = (Subject) PolicyContext.getContext("javax.security.auth.Subject.container");
Principal principal;
if (subject != null) {
Iterator<Principal> principalsIt = subject.getPrincipals().iterator();
while (principalsIt.hasNext()) {
principal = principalsIt.next();
ldapGroups.add(principal.toString());
}
}
}catch (PolicyContextException e) {
...
}
In our new Spring Boot application, after login, we can use the Spring SecurityContextHolder to get user details:
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String currentPrincipalName = authentication.getName();
This is how the user is being authenticated and authorized:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication().userSearchFilter("(...)")
.userSearchBase("...")
.groupSearchBase("...").groupSearchFilter("member={0}").contextSource()
.url("...").managerDn("...").managerPassword("...");
}
#Override
protected void configure(HttpSecurity security) throws Exception {
security.authorizeRequests().antMatchers("/*/**").permitAll().anyRequest().fullyAuthenticated().and()
.formLogin().loginPage("/login").successHandler(new AuthenticationSuccessHandler() {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
redirectStrategy.sendRedirect(request, response, "/campaigns/myCampaigns");
}
});
}
}
Is there a way to modify the code which logs the user in, so that at the same time that they are authenticated and authorized, it also retrieves their groups at the same time. So far, the only examples I have found involve the use of LdapTemplate and making a separate call.
Thanks

Related

How does Spring Security Filter Work With Custom Authentication and How To Combine It with Servlet Filter?

So I have a question regarding Spring Security. So I want to check authentication using custom header which then I want to check the token given in the custom header to redis value and set the data object as credentials at custom implementation of abstract authentication token.
I have already followed the tutorial in this web: https://shout.setfive.com/2015/11/02/spring-boot-authentication-with-custom-http-header/, but I can't update the authentication interface in SecurityContextHolder.getContext() (I set the credentials in my implementation of Authentication Interface, but when I get it in the service, the credentials is null).
I also found other problems, I actually want to order the filter like this:
ExceptionHandlerFilter (to catch exception error in the filter) -> Other filter or CustomWebSecurityConfigurerAdapter.
But when the url matches the antMatcher, I found that ExceptionHandlerFilter was skipped by the application.
I was so confused by this and could not find better tutorial in implementing custom authentication using Spring Security. So I want to ask whether you guys can tell me how Spring Security works and how to combine it with Filter?
Here is my first filter to catch exception
#Component
#Order(0)
public class ExceptionHandlerFilter extends OncePerRequestFilter {
private JaminExceptionHandler exceptionHandler;
private ObjectMapper objectMapper = new ObjectMapper();
#Autowired
public ExceptionHandlerFilter(JaminExceptionHandler exceptionHandler) {
this.exceptionHandler = exceptionHandler;
}
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
try {
filterChain.doFilter(request, response);
} catch (Throwable exception) {
ResponseEntity<?> responseEntity = this.exceptionHandler.handleException(exception, request);
response.setStatus(responseEntity.getStatusCode().value());
response.setHeader("Content-Type", "application/json");
response.getWriter().write(this.objectMapper.writeValueAsString(responseEntity.getBody()));
}
}
}
Here is my Auth Filter
#Component
public class AuthFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String token = request.getHeader("J-Auth");
if (token != null) {
Authentication auth = new JaminAuthenticationToken(token);
SecurityContextHolder.getContext().setAuthentication(auth);
filterChain.doFilter(request, response);
} else {
throw new JaminException("Not authorized", JaminExceptionType.NOT_AUTHORIZED, HttpStatus.UNAUTHORIZED);
}
}
}
Authentication Provider
#Component
public class JaminAuthenticationProvider implements AuthenticationProvider {
private RedisTemplate<String, String> authRedis;
private ObjectMapper objectMapper = new ObjectMapper();
#Autowired
public JaminAuthenticationProvider(#Qualifier("authRedis") RedisTemplate<String, String> authRedis) {
this.authRedis = authRedis;
}
private UserDTO getUserDTO(String token) throws IOException {
String userData = this.authRedis.opsForValue().get(token);
if (userData == null) {
throw new JaminException("Not authorized", JaminExceptionType.NOT_AUTHORIZED, HttpStatus.UNAUTHORIZED);
}
return this.objectMapper.readValue(userData, UserDTO.class);
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
JaminAuthenticationToken auth = (JaminAuthenticationToken) authentication;
try {
UserDTO userDTO = this.getUserDTO(auth.getToken());
auth.setCredentials(userDTO);
return auth;
} catch (IOException e) {
e.printStackTrace();
}
throw new JaminException("Not authorized", JaminExceptionType.NOT_AUTHORIZED, HttpStatus.UNAUTHORIZED);
}
#Override
public boolean supports(Class<?> authentication) {
return JaminAuthenticationToken.class.isAssignableFrom(authentication);
}
}
WebSecurityConfigurerAdapter
#Configuration
#EnableWebSecurity
#Order(1)
public class JaminSecurityAdapter extends WebSecurityConfigurerAdapter {
#Autowired
private JaminAuthenticationProvider jaminAuthenticationProvider;
private void disableDefaultSecurity(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.csrf().disable();
http.formLogin().disable();
http.logout().disable();
http.httpBasic().disable();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
this.disableDefaultSecurity(http);
http.antMatcher("/auth/check")
.authorizeRequests()
.anyRequest().authenticated()
.and()
.addFilterBefore(new AuthFilter(), BasicAuthenticationFilter.class);
// http.authorizeRequests().anyRequest().permitAll();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(jaminAuthenticationProvider);
}
}
Spring Security has some "before and after" steps. There are a few Handlers that can help. I don't know your code, but if you can get your authentication ok, maybe you just have to extend a SuccessHandler and set the authentication there, like i did in my blog project:
if(checkEmail(authentication)) {
val adminRole = SimpleGrantedAuthority("ROLE_ADMIN")
val oldAuthorities = SecurityContextHolder.getContext().getAuthentication().getAuthorities()
val updateAuthorities = mutableListOf<GrantedAuthority>()
updateAuthorities.add(adminRole)
updateAuthorities.addAll(oldAuthorities)
SecurityContextHolder.getContext().setAuthentication(UsernamePasswordAuthenticationToken(authentication.getPrincipal(),
authentication.getCredentials(),
updateAuthorities))
}
And about the filters, maybe you can find your answer here. I don't like using filters and interceptors, but sometimes they are really necessary.

Spring security manually authentication not working

i'm changing an existing app with spring boot, this app not use spring security for authentication, the authentication is a method in a controller, so i want use spring security and i'm trying to use manually authentication in spring security but not working, below you can see the code:
Controller:
#Autowired
#Qualifier(BeanIds.AUTHENTICATION_MANAGER)
private AuthenticationManager authenticationManager;
#PostMapping(value = "/authenticate")
public ResponseEntity<UsuarioRequest> login(#RequestBody UsuarioRequest request, HttpServletRequest servletRequest)
throws AppException {
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(request.getUsulog(), request.getUsupass());
Authentication authentication = authenticationManager
.authenticate(authToken);
SecurityContext context = SecurityContextHolder.getContext();
context.setAuthentication(authentication);
UsuarioRequest usuario = usuarioFacadeAPI.findByUsername(request.getUsulog());
return new ResponseEntity<UsuarioRequest>(usuario, HttpStatus.OK);
}
Security Config:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private SiscoAuthenticationProvider siscoAuthenticationProvider;
#Autowired
public SecurityConfig(SiscoAuthenticationProvider siscoAuthenticationProvider) {
super();
this.siscoAuthenticationProvider = siscoAuthenticationProvider;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(siscoAuthenticationProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin().disable();
http.csrf().disable();
http.authenticationProvider(siscoAuthenticationProvider).authorizeRequests()
.antMatchers("/login/api/**", "/zona/api/**", "/rol/api/**").permitAll()
.anyRequest().authenticated();
}
#Bean(name = BeanIds.AUTHENTICATION_MANAGER)
#Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
}
CustomAuthenticationProvider:
#Component
public class SiscoAuthenticationProvider implements AuthenticationProvider{
private static final String ROLE = "ROLE_";
#Autowired
private UsuarioServiceAPI usuarioServiceAPI;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
UsernamePasswordAuthenticationToken token = null;
try {
UsuarioRequest request = usuarioServiceAPI.authenticate(authentication.getPrincipal().toString(), authentication.getCredentials().toString());
List<RolRequest> rols = request.getRoles();
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
for (RolRequest rol : rols) {
authorities.add(new SimpleGrantedAuthority(ROLE+rol.getRolnom()));
}
token = new UsernamePasswordAuthenticationToken(authentication.getPrincipal(), authentication.getCredentials(), authorities);
} catch (AppException e) {
String message = BundleLoader.getMessage(e.getDetails().getBundle(), e.getDetails().getKey(),
LocaleContextHolder.getLocale());
throw new UsernameNotFoundException(message, e);
}
return token;
}
#Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
}
For the permitAll config no problem occurred, but any other request returns 403 error code even after authentication is success, i suspect that in the controller the SecurityContextHolder not update the authentication, by this the user is always anonymous.
i found a solution for the problem, i changed the Spring Security Config class, specifically the method configure(HttpSecurity http) code below:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin().disable();
http.csrf().disable();
http.authenticationProvider(siscoAuthenticationProvider).authorizeRequests()
.antMatchers("/login/api/**", "/zona/api/**", "/rol/api/**").not().authenticated()
.anyRequest().not().anonymous();
}
the prev config was have problems, with permitAll method and with authenticated method for anyRequest, changing this config for not().authenticated() and not().anonymous() in that order, i get the expected result.

Spring Security REST Login

I got question about login with REST API with Spring Security. As far as login with default login window provided by Spring Security is working and it is authenticating with Database, I have no idea how to make my own login. I know how to substitute the form for my own, but where should I send the data? Should I POST it for some address? I made basic form with username and password.
Try this one, it might help you... at least to understand what you are missing.
This code is not guarantee to be worked 100%, some part is intentionally missed (error handling and it's format, loading user, some checks, Session API).
The basic idea is you must to register a filter (react on all secured request for authentication process) and a provider that later on will be able to load authonticated user and create for you security context (e.g. you know each request is handled per thread and this user can be obtained by SecurityContextHolder/ThreadLocal).
And you need to create a separate controller to handle the initial case for creating a user session aka login/Authorization. Response of this API must to contain some session's GUID to use it as value of header later on: Authentication: Bearer <value>
some spec: https://www.rfc-editor.org/rfc/rfc6750
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)//optional
#Import(RestSecurityConfig.TokenAuthenticationProvider.class)// one of the way to create spring bean
public class RestSecurityConfig extends WebSecurityConfigurerAdapter {
private static final RequestMatcher PUBLIC_URLS = new OrRequestMatcher(
new AntPathRequestMatcher("/actuator/*"),
new AntPathRequestMatcher("/some_api_to_login", POST), // this must be public
);
private static final RequestMatcher PROTECTED_URLS = new NegatedRequestMatcher(PUBLIC_URLS);
// better to move it out as a separate class
public static class TokenAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
#Override
public boolean supports(Class<?> authentication) {
return MyAuthenticationToken.class.isAssignableFrom(authentication);
}
#Override
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
}
#Override
protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
return null; // service/dao.loadUser
}
}
public static class TokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public TokenAuthenticationFilter(RequestMatcher requiresAuthenticationRequestMatcher) {
super(requiresAuthenticationRequestMatcher);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
Authentication auth = new MyAuthenticationToken(request.getHeader("Authentication"));
return getAuthenticationManager().authenticate(auth);
}
}
#Autowired
TokenAuthenticationProvider authenticationProvider;
#Override
protected void configure(final AuthenticationManagerBuilder auth) {
auth.authenticationProvider(authenticationProvider);
}
#Override
public void configure(final WebSecurity web) {
web.ignoring().requestMatchers(PUBLIC_URLS);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// maybe some of the tuning you might not need
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.exceptionHandling()
.defaultAuthenticationEntryPointFor(new Http403ForbiddenEntryPoint(), PROTECTED_URLS).and()
.authorizeRequests().anyRequest().authenticated().and()
.cors().and()
.anonymous().disable()
.rememberMe().disable()
.csrf().disable()
.formLogin().disable()
.httpBasic().disable()
.logout().disable();
// it's important
http.addFilterBefore(tokenAuthenticationFilter(), AnonymousAuthenticationFilter.class);
}
#Bean
AbstractAuthenticationProcessingFilter tokenAuthenticationFilter() throws Exception {
final AbstractAuthenticationProcessingFilter filter = new TokenAuthenticationFilter(PROTECTED_URLS);
filter.setAuthenticationManager(authenticationManager());
filter.setAuthenticationSuccessHandler(successHandler());
// maybe error handling to provide some custom response?
return filter;
}
// it's critically important to register your filter properly in spring context
/** Disable Spring boot automatic filter registration. */
#Bean
FilterRegistrationBean disableRegistrationForAuthenticationFilter(final TokenAuthenticationFilter filter) {
final FilterRegistrationBean registration = new FilterRegistrationBean(filter);
registration.setEnabled(false);
return registration;
}
// this one also is critically important to avoid redirection
#Bean
SimpleUrlAuthenticationSuccessHandler successHandler() {
final SimpleUrlAuthenticationSuccessHandler successHandler = new SimpleUrlAuthenticationSuccessHandler();
successHandler.setRedirectStrategy(new NoRedirectStrategy());
return successHandler;
}
}
You can store usernames and passwords in database, which you can use to login users. You create your own class which extends WebSecurityConfigurerAdapter and override methods which you need to modify:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
DataSource dataSource;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
auth.jdbcAuthentication()
.dataSource(dataSource)
}
}
But be vary of Spring Security default database query when searching for usernames and passwords so you can create database schema which will be good:
public static final String DEF_USERS_BY_USERNAME_QUERY =
"select username,password,enabled " +
"from users " +
"where username = ?";
public static final String DEF_AUTHORITIES_BY_USERNAME_QUERY =
"select username,authority " +
"from authorities " +
"where username = ?";
public static final String DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY =
"select g.id, g.group_name, ga.authority " +
"from groups g, group_members gm, group_authorities ga " +
"where gm.username = ? " +
"and g.id = ga.group_id " +
"and g.id = gm.group_id";
But you can also use Spring methods to specify your own query to database:
auth
.jdbcAuthentication()
.dataSource(dataSource)
.usersByUsernameQuery(
"select username, password, enabled from Users " +
"where username=?")
You should POST your data to some service you created which will store user and pass to a database.

Spring security : Custom authentication provider not working

I am using custom authentication provider which implements "AuthenticationProvider". In SecurityConfig i am using following configs.
http.csrf().disable().authorizeRequests()
.antMatchers("/login/authenticateUser").permitAll()
.and()
.authorizeRequests().anyRequest().authenticated();
The above configuration does not call custom authentication provider for login API but for other API's the custom authetication provider is not being called which is throwing forbidden error.
#Autowired
private CustomAuthenticationProvider authProvider;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.antMatchers("/login/authenticateUser").permitAll()
.and()
.authorizeRequests().anyRequest().authenticated();
}
Custom authentication provider:
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
static Map<String, UserDetails> userSessionList = new HashMap<String, UserDetails>();
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String userName = authentication.getName();
String password = authentication.getCredentials().toString().split(";")[0];
if (checkUserNameAndPassword(userName, password)) {
List<GrantedAuthority> grantedAuths = new ArrayList<>();
grantedAuths.add(() -> {
return "AUTH_USER";
});
Authentication auth = new UsernamePasswordAuthenticationToken(userName, password, grantedAuths);
return auth;
} else {
throw new AuthenticationCredentialsNotFoundException("Invalid Credentials!");
}
}
#Override
public boolean supports(Class<?> authentication) {
//return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
Please help me to solve this issue. Custom authentication provider should be called for all the requests except login controller.

How to implement UsernamePasswordAuthenticationFilter for email field instead of username in Spring Security

There is UsernamePasswordAuthenticationFilter in SpringSecurity itself but I want to use email instead of username.
I had used this class for username like this :
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter{
private AuthenticationManager authenticationManager;
private JwtUserDetailsToUserDTOConverter jwtUserDetailsToUserDTOConverter = new JwtUserDetailsToUserDTOConverter();
public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
try {
User user = new ObjectMapper().readValue(request.getInputStream(), User.class);
return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword()));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication authResult) throws IOException, ServletException {
String token = JwtGenerator.builder().build().generate((JwtUserDetails) authResult.getPrincipal());
response.addHeader(JwtConstants.HEADER,JwtConstants.PREFIX + token);
}
}
I want to implement AuthenticationFilter for email and password. There is no any specified class for this intent inside of SpringSecurity.
How can I customize it for email?
UsernamePasswordAuthenticationFilter just extracts username (which could be email) and password from request and forwards it to the configured AuthenticationManager.
The default (assuming you haven't overridden it), implementation of AuthenticationManager used is ProviderManager.
ProviderManager in turn is configured with an AuthenticationProvider. This would be DaoAuthenticationProvider in most cases.
From DaoAuthenticationProvider, call reaches UserDetailsService#loadUserByUsername() which tries to look up a user from DB based on given username.
So you have couple of options here:
Create a custom UserDetailsService, with overridden loadUserByUsername(), which matches the given username against user's email. This can then be set into AuthenticationManagerBuilder instance like this:
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
{
auth.userDetailsService(myUserDetailsService);
}
}
Custom AuthenticationProvider which validates user against email. This is a bit of an overkill for your scenario and would result in duplication of code. But if you want to do this, use the same code as #1 but invoke auth.authenticationProvider(myAuthProviderInstance); instead

Resources