Extension point for AuthenticationManager - java

I am using Spring Security for simple log in:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebAuthenticationConfig extends WebSecurityConfigurerAdapter {
private final PasswordEncoder passwordEncoder;
private final UserDetailServiceImpl userDetailsService;
#Autowired
public WebAuthenticationConfig(PasswordEncoder passwordEncoder, UserDetailServiceImpl userDetailsService) {
this.passwordEncoder = passwordEncoder;
this.userDetailsService = userDetailsService;
}
#Override
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.cors()
.and()
.csrf()
.disable()
.authorizeRequests()
.antMatchers(
"/register/*",
"/login").permitAll()
.anyRequest().authenticated()
.and()
.exceptionHandling()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
As you can see i am using default AuthManager. Now the UserDetailServiceImpl is simple:
#Service
public class UserDetailServiceImpl implements UserDetailsService {
private final UserRepository userRepository;
#Autowired
public UserDetailServiceImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
Optional<UserEntity> dbUser = userRepository.findUserByEmail(email);
if (!dbUser.isPresent()) {
throw new UsernameNotFoundException("No user found with email " + email);
}
UserEntity user = dbUser.get();
return UserDetailsImpl.build(user);
}
}
And in my controller i use it such as:
#RequestMapping(value = "login", method = RequestMethod.POST)
public void loginUser(#Valid #RequestBody LoginUserRequest request, HttpServletRequest httpServletRequest) {
Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(request.getEmail(), request.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
This works just fine, authentication manager calls getUserByEmail of UserDetailServiceImpl, finds user, and then authenticationManager compares passwords and returning auth object.
However, what if i want to do another operations during this process? What if i want to log IP address of the user being logged?
I can extract IP address from HttpServletRequest in controller, however where is extension point for AuthenticationManager where i actually could plug in this functionality?
I could extend AbstractAuthenticationToken to accept details about Token ( so in my case i would pass, besides email and password , IP address to authenticate).
And then i could implement my own AuthenticationManagerso it would not require implementation of my UserDetailService and thus my AuthenticationManager could look like:
public class MyAuthenticationManager implements AuthenticationManager{
#Autowired
private final UserRepository;
#Autowired
private final PasswordEncoder passwordEncoder;
Authentication authenticate(Authentication authentication) throws AuthenticationException{
MyAuthToken token = (MyAuthToken) authentication;
UserEntity user = userRepository.findUserByEmail(token.getName);
// check if user exists and if password matches
userRepository.addIp(token.getIP());
}
}
But then i would lose whole flow of authentication manager, which i am not sure if its good since it seems robust.
Is there any extension point that i could plug my functionality into?
Thanks for help

Related

Spring security passes complete decoded jwt token to loadUserByUsername method how can I get it to pass only username from the token instead?

I've encoded additional data to jwt token like user authorities,username etc. Spring security is invoking loadUserByUsername with complete jwt token decoded how can I get it to pass only username from the token to userdetailsService?
code
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("myUserDetailsService")
private UserDetailsService myUserDetailsService;
#Autowired
private MessageSource messageSource;
#Autowired
private JwtRequestFilter jwtRequestFilter;
#Autowired
private CustomAuthenticationProvider authProvider;
#Autowired
protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider).userDetailsService(myUserDetailsService).passwordEncoder(new PlainTextPasswordEncoder());
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.cors()
.and()
.authorizeRequests()
.antMatchers("/api/authenticate")
.permitAll()
.anyRequest()
.authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint())
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);//NOSONAR
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/swagger-ui/**", "/v3/api-docs/**", "/swagger-ui.html");
}
#Bean
public AuthenticationEntryPoint authenticationEntryPoint() {
return new CustomAuthenticationEntryPoint(messageSource);
}
}
UserDetailsService
#Service("myUserDetailsService")
public class MyUserDetailsService implements UserDetailsService {
#Value("${jwt.password.secret}")
public String password;
#Autowired
AekAztabRepository aekAztabRepository;
#Autowired
ConversionService conversionService;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<Aektab> aekAztab = aektabRepository.findByGeNr(username);
if(aektab.isPresent()) {
if (aekAztab.get().getGeNr().equals(username)) {
return conversionService.convert(aektab.get(), User.class);
}
}
else{
throw new UsernameNotFoundException("Invalid user name");
}
return null;
}
public List<UserDetails> populateUserDetails()
{
List<UserDetails> userDetails =new ArrayList<>();
return userDetails;
}
}
the username that is passed to loadUserByUsername is username={"username":"123456788","language":null,"deactivationReason":null,"terminationYear":null,"authorities":[{"authority":"ROLE_ADMIN_ROK"}],"active":false,"terminated":false}
how can I override security configurations to get spring security to pass only username from this token ? And which one is called first AuthenticationProvider or UserdetailsService?
update
AuthenticationProvider
#Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
String name = authentication.getName();
String password = authentication.getCredentials().toString();
// use the credentials
// and authenticate against the third-party system
return new UsernamePasswordAuthenticationToken(
name, password, new ArrayList<>());
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}

Spring Security add a custom filter chain just for for Registration API [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 1 year ago.
Improve this question
I'm building a Rest application and I need to apply a filter(this filters will do the validation of the credentials like email is valid, the username is not already toked, the password is strong and match confirmationPassword).
I want to apply this chain just for Registration and no other filter (like check if you are authenticated)
I have something like this in spring securityConfiguration
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final BCryptPasswordEncoder passwordEncoder;
private final UserServiceImplementation serviceImplementation;
private final JwtConfiguration jwtConfiguration;
#Autowired
public SecurityConfiguration(UserServiceImplementation serviceImplementation, BCryptPasswordEncoder passwordEncoder, JwtConfiguration jwtConfiguration) {
this.serviceImplementation = serviceImplementation;
this.passwordEncoder = passwordEncoder;
this.jwtConfiguration = jwtConfiguration;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilter(new JwtUsernameAndPasswordAuthenticationFilter(authenticationManager(), jwtConfiguration))
.addFilterAfter(new JwtTokenVerifier(jwtConfiguration), JwtUsernameAndPasswordAuthenticationFilter.class)
.authorizeRequests()
.anyRequest()
.authenticated();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(daoAuthenticationProvider());
}
#Override
public void configure(WebSecurity web) {
web.debug(true);
web.ignoring().antMatchers(HttpMethod.GET, "/register/**");
web.ignoring().antMatchers(HttpMethod.POST, "/register/**");
}
#Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setPasswordEncoder(passwordEncoder);
provider.setUserDetailsService(serviceImplementation);
return provider;
}
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("HEAD", "GET", "PUT", "POST", "DELETE", "PATCH");
}
};
}
}
I will appreciate any input and any suggestions from the community!
You can create a POST mapping controller method to handle validation of registration. Firstly you should permit url of registration as follows in your SpringSecurityConfig.
#Autowired
private UserRepository userRepository;
#Override
public UserDetailsService userDetailsServiceBean() throws Exception {
return new CustomUserDetailsServiceImpl(userRepository);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().and()
.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/api/v1/user/register").permitAll();
}
In UserController:
#RestController
#RequestMapping("/api/v1/user")
public class UserController {
#Autowired
private CustomUserDetailsServiceImpl userDetailsService;
#Autowired
public UserController(CustomUserDetailsServiceImpl userDetailsService) {
this.userDetailsService = userDetailsService;
}
#PostMapping(value = "/register")
public User register(#RequestBody User user) {
return userDetailsService.save(user);
}
}
UserRepository:
public interface UserRepository extends JpaRepository<User, String> {
User findByUsername(String username);
}
In CustomUserDetailsServiceImpl: You can filter validation in this implementation (email is valid, the username is not already toked, the password is strong and match confirmationPassword)
public User save(User user){
if(StringUtils.isEmpty(user.getUsername())) {
throw ExceptionFactory.getApiError(ExceptionEnum.BAD_REQUEST, "username");
}
if(StringUtils.isEmpty(user.getPassword())) {
throw ExceptionFactory.getApiError(ExceptionEnum.BAD_REQUEST, "password");
}
User registeredUser = new User();
registeredUser.setUsername(user.getUsername());
registeredUser.setPassword(passwordEncoder.encode(user.getPassword()));
registeredUser.setEnabled(true);
registeredUser.setRoles(Arrays.asList(new Role(RoleEnum.USER.getRole())));
return userRepository.save(registeredUser);
}

Spring security authentication server

I am working on authentication service part of cloud app and I created the following security config class.
#Configuration
#EnableWebSecurity
public class JwtSecurityConfig extends WebSecurityConfigurerAdapter {
private final PasswordEncoder encoder;
private final UserService userService;
private final JwtConstant jwtConstant;
#Autowired
public JwtSecurityConfig(PasswordEncoder encoder, UserService userService, JwtConstant jwtConstant) {
this.encoder= encoder;
this.userService = userService;
this.jwtConstant = jwtConstant;
}
#Bean
public DaoAuthenticationProvider getAuthenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setPasswordEncoder(encoder);
authenticationProvider.setUserDetailsService(userService);
return authenticationProvider;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(getAuthenticationProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilter(getAuthenticationFilter())
.authorizeRequests()
.antMatchers(HttpMethod.PUT, "/signup").permitAll()
.anyRequest()
.authenticated();
}
private AuthenticationFilter getAuthenticationFilter() throws Exception {
return new AuthenticationFilter(authenticationManager(), jwtConstant);
}
}
I am not sure about the chain methods of configure(HttpSecurity http) method. The authentication service will only receive "login" and "signup" requests.
Should I remove authorizeRequests() method as I do not authorize anything?
I am not sure about anyRequest().authenticated() part either if I really need it?
there are a couple of things that have to be changed, but first of all, you have to define a method that will provide jwt for each request and every request should provide an AuthRequest object that contains username and password :
#RestController
public class WelcomeController {
#Autowired
private JwtUtil jwtUtil;
#Autowired
private AuthenticationManager authenticationManager;
#PostMapping("/signup")
public String generateToken(#RequestBody AuthRequest authRequest) throws Exception {
try {
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(authRequest.getUserName(), authRequest.getPassword())
);
} catch (Exception ex) {
throw new Exception("inavalid username/password");
}
return jwtUtil.generateToken(authRequest.getUserName());
}
}
and in the UserDetailsService you can make authentication as below :
#Service
public class UserDetailsService implements org.springframework.security.core.userdetails.UserDetailsService {
#Autowired
private final UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("tried to loging : " + username);
if(!Objects.isNull(username) && !"".equals(username)){
Optional<User> user = userRepository.findUserByUserName(username);
System.out.println(user.get());
if(user.isPresent()){
User userParam = user.get();
return new org.springframework.security.core.userdetails.User(userParam.getUserName(),
userParam.getPassword(), new ArrayList<>());
}
}
throw new UsernameNotFoundException("user does not exists or empty !!");
}
}
and for the configuration side :
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private final UserDetailsService userDetailsService;
#Autowired
private final JwtFilter jwtFilter;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder(10);
}
#Bean(name = BeanIds.AUTHENTICATION_MANAGER)
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests().antMatchers("/signup").permitAll()
.anyRequest().authenticated()
.and().exceptionHandling().and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);;
}
}
for further information, you can follow my Github branch Authnticaition sample

How to configure multiple HttpSecurity with UserDetailsService using spring boot security?

I'm working on with spring boot security layer to authenticate and authorize the user.Now, i would like to do some sample app using multi http security configuration.I have the scenario like there will be two login pages with different URL mappings("/managementLogin","/othersLogin").
I can understood how to configure multi httpsecurity configs but i need to validate the users from two tables.If the management users loggedIn i need to validate the user from management table through DAO layer using UserDetailsService else if any other users loggedIn i need to validate from other_users table.
Could anybody help me to know how to configure the multi http config and dao layer using UserDetailsService with spring boot security ?
Here is my basic code snippet,
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class ApplicationSecurity extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("userDetailsService")
UserDetailsService userDetailsService;
#Autowired
private RESTAuthenticationEntryPoint authenticationEntryPoint;
#Autowired
private RESTAuthenticationFailureHandler authenticationFailureHandler;
#Autowired
private RESTAuthenticationSuccessHandler authenticationSuccessHandler;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// for testing authentication purpose using inMemory db
/*
* auth.inMemoryAuthentication().withUser("user").password("user").roles
* ("USER").and().withUser("admin") .password("admin").roles("ADMIN");
*/
// Dao based authentication
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/", "/home").permitAll();
http.authorizeRequests().antMatchers("/rest/**").authenticated();
http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
http.formLogin().successHandler(authenticationSuccessHandler);
http.formLogin().failureHandler(authenticationFailureHandler);
http.logout().logoutSuccessUrl("/");
// CSRF tokens handling
http.addFilterAfter(new CsrfTokenResponseHeaderBindingFilter(), CsrfFilter.class);
}
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/registerUser","/register.html");
}
#Bean
public PasswordEncoder passwordEncoder() {
PasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder;
}
}
TIA..,
Implement a custom UserDetailsService like this:
#Service
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private UserDaoTableOne userDaoTableOne;
#Autowired
private UserDaoTableTwo userDaoTableTwo;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserDetails user = userDaoTableOne.find(username);
if(user == null){
user = userDaoTableTwo.find(username);
}
if (user == null) {
throw new UsernameNotFoundException(String.format("Username '%s' not found", username));
}
return user;
}
}
Implement two DaoAuthenticationProvider with his own UserDetailsService and inject both providers to the authenticationManager.
I don't know what is the requisite for two distinct login endpoints but at first I think is a bad idea.
You can create different Authentication objects an let the AuthenticationManager choose the correct AuthenticationProvider based in the supports method.
Indeed you will need to use, two user detail services. But, that wont be enough. I suggest you to create another ApplicationSecurity2 class with different order.
Spring security is built on an ordered list of filter chains.
see the answer given here by Dave Sayer. Then you can handle different urls, as you want.
in my case I checked into two repositories, Below an exemple that I use:
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
AbstractUser user;
try {
user = clientRepository.findByUsername(username);
}
catch (Exception userException) {
try {
user = adminRepository.findByUsername(username);
}
catch (Exception adminException) {
throw new UsernameNotFoundException("No user present with username : " + username);
}
}
return user;
}
I have to handle around same issue , i have autowired httprequest class in userdetail service and get request params type and drive my logic based on that.
you can directly solve the issue as the recommended solutions, but you can create a simple trick to define two different UserDetailsService as here I have two user one as a normal user and another as an editor :
editor
#Log4j2
#RequiredArgsConstructor
#Service
public class EditorService implements UserDetailsService {
private final EditorRepository editorRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if(username == null || "".equals(username)){
throw new UsernameNotFoundException("null value");
}
Optional<Editor> editor = editorRepository.findByUsername(username);
if(editor.isPresent()){
log.info("created under editor service: " + editor.get());
return editor.get();
}
throw new UsernameNotFoundException("does not exists");
}
}
user
#Log4j2
#RequiredArgsConstructor
#Service
public class UserService implements UserDetailsService {
private final UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if(username == null || "".equals(username)){
throw new UsernameNotFoundException("null");
}
Optional<User> user = userRepository.findByUsername(username);
if(user.isPresent()){
log.info("cretaed under User service : " + user.get());
return user.get();
}
throw new UsernameNotFoundException("does not exists");
}
}
then on the configurations side, we can use of spring order mechanism :
user config :
#EnableWebSecurity
#Configuration
#RequiredArgsConstructor
#Order(1)
public class UserWebSecurityConfig extends WebSecurityConfigurerAdapter {
private final UserService userService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers()
.antMatchers("/user/**")
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic();
}
#Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder(10);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(this.userService).passwordEncoder(passwordEncoder());
}
}
Editor config :
#EnableWebSecurity
#Configuration
#RequiredArgsConstructor
public class EditorWebSecurityConfig extends WebSecurityConfigurerAdapter {
private final EditorService editorService;
#Lazy
private final PasswordEncoder passwordEncoder;
#Override
protected void configure(HttpSecurity http) throws Exception {
http // all other requests handled here
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(this.editorService).passwordEncoder(passwordEncoder);
}
}

Spring Security service configuration

I'm trying to build a Java EE app prototype using different frameworks. Everything works fine except the security layer. I chose to use Spring Security configured with Spring configuration.
The code is like this:
Spring Security Config
#Configuration
#EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
#Autowired
private MyUserDetailsService userDetailsService;
#Override
protected UserDetailsService userDetailsService () {
return this.userDetailsService;
}
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/resources/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/login/authenticate")
.failureUrl("/login?error=bad_credentials")
.and()
.logout()
.logoutUrl("/signout")
.deleteCookies("JSESSIONID")
.and()
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/**").permitAll()
.and()
.csrf();
}
}
User Detail Service
#Service("myUserDetailsService")
public class MyUserDetailsService implements UserDetailsService
{
public static final Logger log = Logger.getLogger(MyUserDetailsService.class);
public MyUserDetailsService() {
}
#Autowired
private UserDao userDao;
#Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
final User user = getSystemUser(userName);
final List<GrantedAuthority> authorities = getUserAuthorities(user);
return buildUserForAuthentication(user, authorities);
}
private User buildUserForAuthentication(User user, List<GrantedAuthority> authorities) {
//...
}
private User getSystemUser(String alias) {
//...
}
private List<GrantedAuthority> getUserAuthorities(User user) {
//...
return null;
}
}
What I'm expecting this code to do is that when /login/authenticate is reached with the user & pass params, the underlying spring code invokes my user service, but this never happens.
What am I missing?
I'm using spring-security 3.2.3.RELEASE.
You should register your custom authentication in SecurityConfig class which have extended WebSecurityConfigureAdapter:
#Autowired
private MyUserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.userDetailsService(this.userDetailsService);
}
for 3.2.3 the config should be
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(this.userDetailsService);
}

Categories

Resources