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
Related
I have added BCryptPasswordEncoder to my project and I store every user's password in my DB in encoded way. And now I can't log in using not encoded password but if I use encoded password I still can log in. As I know, AuthenticationManager have to encode entered password according to bean PasswordEncoder and compare with each password in my DB.
So the question is: why AuthenticationManager doesn't encode entered password?
P.S: As I know, authenticationConfiguration.getAuthenticationManager() replaces authenticationConfiguration.userDetailsService(personDetailsService).passwordEncoder(getPasswordEncoder()) since Spring Security 5.7.0-M2.
My SecurityConfig class:
#EnableWebSecurity
public class SecurityConfig {
private final PersonDetailsService personDetailsService;
#Autowired
public SecurityConfig(PersonDetailsService personDetailsService) {
this.personDetailsService = personDetailsService;
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests().antMatchers("/auth/login", "/error", "/auth/registration").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/auth/login")
.loginProcessingUrl("/process_login")
.defaultSuccessUrl("/hello", true)
.failureUrl("/auth/login?error")
.and()
.logout().logoutUrl("/logout").logoutSuccessUrl("/auth/login");
return http.build();
}
#Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
My RegistrationService:
#Service
public class RegistrationService {
private final PeopleRepository peopleRepository;
private final PasswordEncoder passwordEncoder;
#Autowired
public RegistrationService(PeopleRepository peopleRepository, PasswordEncoder passwordEncoder) {
this.peopleRepository = peopleRepository;
this.passwordEncoder = passwordEncoder;
}
#Transactional
public void register(Person person) {
person.setPassword(passwordEncoder.encode(person.getPassword()));
peopleRepository.save(person);
}
}
EDIT:
If I extend my SecurityConfig from deprecated WebSecurityConfigurerAdapter and change my code to this:
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final PersonDetailsService personDetailsService;
#Autowired
public SecurityConfig(PersonDetailsService personDetailsService) {
this.personDetailsService = personDetailsService;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests().antMatchers("/auth/login", "/error", "/auth/registration").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/auth/login")
.loginProcessingUrl("/process_login")
.defaultSuccessUrl("/hello", true)
.failureUrl("/auth/login?error")
.and()
.logout().logoutUrl("/logout").logoutSuccessUrl("/auth/login");
}
#Bean
public PasswordEncoder getPasswordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(personDetailsService).passwordEncoder(getPasswordEncoder());
}
}
then it works like it have to (entered by user password is getting encrypted by configure(AuthenticationManagerBuilder auth) and then it compares to other passwords from DB and if there is the same password found user is authenticated).
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);
}
}
I have spring web mvc project with Spring Security 4.1.0.RELEASE
In spring controller i try fetch the user from the context
#RestController
public class Test {
#RequestMapping(value="test", method = RequestMethod.POST)
public ResponseEntity<Void> test() {
ContextUser user = (ContextUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}
}
user is an instance with id = 0, login = null .... It is empty instance.
org.springframework.security.core.Authentication isAuthenticated = true, list of Collection<? extends GrantedAuthority> are correct This behaviour is occurred periodically. It is not every time for this request. I catch this issue only for that request
My configurations
#Configuration
#ComponentScan(basePackages={"com.test.app"},
excludeFilters=#ComponentScan.Filter(type=FilterType.REGEX, pattern={"com.test.app.web.*"}))
#PropertySource(value = { "classpath:application.properties" })
#EnableAspectJAutoProxy
public class AppConfig {
#Autowired
private DataSource dataSource;
//My beans
}
#Component
public class TestUserDetailsService implements UserDetailsService{
#Autowired
private TestUserService service;
#Override
public UserDetails loadUserByUsername(String userName)
throws UsernameNotFoundException {
User user = service.findByLogin(userName);
if (user == null) {
throw new UsernameNotFoundException("Error");
}
return new ContextUser(user);
}
}
public class ContextUser extends User implements UserDetails {
//...
}
#Configuration
#EnableWebSecurity
#EnableAsync
#EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
#Autowired
private TestAuthenticationEntryPoint testAuthenticationEntryPoint;
#Autowired
private TestSimpleUrlAuthenticationSuccessHandler testSimpleUrlAuthenticationSuccessHandler;
#Autowired
private TestSimpleUrlAuthenticationFailureHandler testSimpleUrlAuthenticationFailureHandler;
#Autowired
private LogoutSuccessHandler logoutSuccessHandler;
#Autowired
private TestUserDetailsService testUserDetailsService;
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder registry) throws MyException {
registry.userDetailsService(testUserDetailsService).passwordEncoder(new TestEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(new CorsFilter(), ChannelProcessingFilter.class);
http.csrf()
.disable()
.exceptionHandling()
.authenticationEntryPoint(testAuthenticationEntryPoint)
.and().sessionManagement().sessionFixation().migrateSession().maximumSessions(-1).sessionRegistry(sessionRegistry()).and()
.and()
.authorizeRequests()
.antMatchers("/").access("hasAuthority('TEST')")
.anyRequest().authenticated()
.and()
.formLogin()
.successHandler(testSimpleUrlAuthenticationSuccessHandler)
.failureHandler(testSimpleUrlAuthenticationFailureHandler)
.and()
.logout().logoutSuccessHandler(logoutSuccessHandler)
.and()
.headers().cacheControl().disable().frameOptions().sameOrigin();
}
#Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
#Override
public void configure(WebSecurity web) {
web.ignoring().antMatchers("/resources/**");
}
}
Are any ideas why this behaviour is happened?
I'm trying to get an endpoint not accessible (503 error?) without Authorization: Bearer token header
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.cors().and().csrf().disable()
.authorizeRequests()
.antMatchers("/authenticate")
.permitAll()
.antMatchers("/api/admin/**")
.fullyAuthenticated()
.anyRequest().authenticated().and().
exceptionHandling()
.and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests();
httpSecurity.addFilterBefore(jwtRequestFilter,
UsernamePasswordAuthenticationFilter.class);
}
#RestController
#CrossOrigin
#RequestMapping("/api/admin")
public class AdminController {
#RequestMapping("/test")
public String testAdmin() {
return "OK; secret test admin";
}
}
however I can access it just fine
What should I change in my configure method?
EDIT:
#Component
public class JwtRequestFilter extends OncePerRequestFilter {
#Autowired
private UserDetailsServiceImpl userDetailsService;
#Autowired
private JwtUtil jwtUtil;
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String authorizationHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwt = authorizationHeader.substring(7);
username = jwtUtil.extractUsername(jwt);
}
if (username != null && SecurityContextHolder
.getContext().getAuthentication() == null) {
UserDetails userDetails = this
.userDetailsService.loadUserByUsername(username);
if (jwtUtil.validateToken(jwt, userDetails)) {
UsernamePasswordAuthenticationToken
usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
It seems that the jwtRequestFilter's doFilterInternal method never runs: I tried setting the breakpoints in the debugger and the execution never stopped there.
EDIT: whole SecurityConfig:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private UserDetailsService userDetailsService;
#Autowired
private JwtRequestFilter jwtRequestFilter;
#Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
public SecurityConfig(
UserDetailsServiceImpl userDetailsService) {
this.userDetailsService = userDetailsService;
}
#Bean
DaoAuthenticationProvider authenticationProvider(){
DaoAuthenticationProvider daoAuthenticationProvider =
new DaoAuthenticationProvider();
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
daoAuthenticationProvider.setUserDetailsService(this.userDetailsService);
return daoAuthenticationProvider;
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring();
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
#Autowired
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable()
// dont authenticate this particular request
.authorizeRequests().antMatchers("/api/login").permitAll()
// all other requests need to be authenticated
.anyRequest().authenticated().and()
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// Add a filter to validate the tokens with every request
httpSecurity.addFilterBefore(jwtRequestFilter,
UsernamePasswordAuthenticationFilter.class);
}
#Bean
BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
HTTP response 503 means service unavailable. You should get 401 Unauthorized when token is missing.
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
#Autowired
private UserDetailsService jwtUserDetailsService;
#Autowired
private JwtRequestFilter jwtRequestFilter;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable()
// dont authenticate this particular request
.authorizeRequests().antMatchers("/login").permitAll()
// all other requests need to be authenticated
.anyRequest().authenticated().and()
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// Add a filter to validate the tokens with every request
httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
}
Using AuthenticationEntryPoint.
#Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {
private static final long serialVersionUID = -1L;
#Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
}
I managed to solve it. Turns out the problem was in me not having correct configurations, so the SecurityConfig never even got applied. I fixed it this way:
WebConfig.java:
#Configuration
#ComponentScan("testproject")
#EnableWebMvc
#EnableTransactionManagement
#EnableJpaRepositories(basePackages = "testproject",
entityManagerFactoryRef = "entityManagerFactory", transactionManagerRef = "transactionManager")
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void configureDefaultServletHandling(
DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
}
#Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver bean = new InternalResourceViewResolver();
bean.setViewClass(JstlView.class);
bean.setPrefix("/WEB-INF/view/");
bean.setSuffix(".html");
return bean;
}
#Bean
public UserDetailsService userDetailsService() {
UserDetailsService userDetailsService =
new UserDetailsServiceImpl();
return userDetailsService;
}
}
MyAppInitializer.java (notice the commented out sc.addListener(new ContextLoaderListener(root)); line, it must be like that, otherwise there are errors - the fix was suggested to me in another SO question):
public class MyAppInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
#Override
public void onStartup(final ServletContext sc) throws ServletException {
System.out.println("onStartup!");
AnnotationConfigWebApplicationContext root =
new AnnotationConfigWebApplicationContext();
root.register(WebConfig.class);
root.setServletContext(sc);
root.scan("testproject");
//sc.addListener(new ContextLoaderListener(root));
ServletRegistration.Dynamic appServlet =
sc.addServlet("dispatcher", new DispatcherServlet(new GenericWebApplicationContext()));
appServlet.setLoadOnStartup(1);
appServlet.addMapping("/");
}
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] {SecurityConfig.class};
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{WebConfig.class};
}
#Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
SecurityWebApplicationInitializer.java:
public class SecurityWebApplicationInitializer extends
AbstractSecurityWebApplicationInitializer {
public SecurityWebApplicationInitializer() {
super(SecurityConfig.class, WebConfig.class);
}
}
I want to use both in memory auth and userDetailsService auth. Beacause I have to create one admin account when application start and then I will create another one with form. But the first one had to in memory due to security reason.
SecurityConfig.java
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
/* #formatter:off */
http.authorizeRequests()
// some configs
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(this.userDetailsService)
.passwordEncoder(passwordEncoder())
.and()
.inMemoryAuthentication()
.passwordEncoder(passwordEncoder())
.withUser("user").password("1").roles("ADMIN");
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
UserDetailsServiceImpl
#Service
#Transactional
public class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
private VendorService vendorService;
#Autowired
private AdminService adminService;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserDetails userAdmin = adminService.getAdminByUsername(username);
if (userAdmin != null) {
System.out.println(userAdmin.getUsername());
return userAdmin;
} else {
UserDetails userVendor = vendorService.getVendorByUsername(username);
if (userVendor == null) {
throw new UsernameNotFoundException("No such user");
}else{
System.out.println(userVendor.getUsername());
return userVendor;
}
}
}
}
I can login with in DB users but can't login with user and 1 password.