I using spring security and jwt . I want login two way : One way by email, password and one way by phone and passcode. If user login by email when login success spring auto return token to user. It same way when user login by phone. I has write code login by email and it login by success and token return to user but i can't know how to implement by phone and passcode. When i implement by mutiple http login form, spring throw me error look like :
Field authenticationManager in com.baotrung.controller.JwtAuthenticationController required a single bean, but 2 were found:
- emailAuthenticationManager: defined by method 'authenticationManagerBean' in class path resource [com/baotrung/config/MultipleSecurityConfig$ApiConfigurationAdapter.class]
- PhoneAuthenticationManager: defined by method 'authenticationManagerBean' in class path resource [com/baotrung/config/MultipleSecurityConfig$PhoneConfigurationAppter.class]
Action:
Consider marking one of the beans as #Primary, updating the consumer to accept multiple beans, or using #Qualifier to identify the bean that should be consumed
Bellow my code
#Configuration
#EnableWebSecurity
public class MultipleSecurityConfig {
#Bean
public static PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Configuration
#Order(1)
public static class ApiConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
#Autowired
private UserDetailsService jwtUserDetailsService;
#Autowired
private JwtRequestFilter jwtRequestFilter;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
// configure AuthenticationManager so that it knows from where to load
// user for matching credentials
// Use BCryptPasswordEncoder
auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder());
}
#Bean(name = "emailAuthenticationManager")
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
// We don't need CSRF for this example
httpSecurity.csrf().disable()
// dont authenticate this particular request
.authorizeRequests().antMatchers("/authenticate", "/register").permitAll().
// all other requests need to be authenticated
anyRequest().authenticated().and().
// make sure we use stateless session; session won't be used to
// store user's state.
exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// Add a filter to validate the tokens with every request
httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
}
#Configuration
#Order(100)
public static class PhoneConfigurationAppter extends WebSecurityConfigurerAdapter {
#Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
#Autowired
private JwtUserPhoneDetailsService jwtUserPhoneDetailsService;
#Autowired
private JwtPhoneRequestFilter jwtPhoneRequestFilter;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
// configure AuthenticationManager so that it knows from where to load
// user for matching credentials
// Use BCryptPasswordEncoder
auth.userDetailsService(jwtUserPhoneDetailsService).passwordEncoder(passwordEncoder());
}
#Bean(name = "PhoneAuthenticationManager")
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
// We don't need CSRF for this example
httpSecurity.csrf().disable()
// dont authenticate this particular request
.authorizeRequests().antMatchers("/phone/authentication", "/phone/register").permitAll().
// all other requests need to be authenticated
anyRequest().authenticated().and().
// make sure we use stateless session; session won't be used to
// store user's state.
exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// Add a filter to validate the tokens with every request
httpSecurity.addFilterBefore(jwtPhoneRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
}
}
Entity
User
public class UserDTO {
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
PhoneUser
#Entity
#Table(name = "user_phone")
public class PhoneUser {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column
private String phone;
#Column
#JsonIgnore
private String passcode;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getPasscode() {
return passcode;
}
public void setPasscode(String passcode) {
this.passcode = passcode;
}
}
EmailJwtTokenFilter
#Slf4j
public class JwtAuthenticationFilter extends OncePerRequestFilter {
#Autowired
private JwtTokenProvider tokenProvider;
#Autowired
private CustomUserDetailsService customUserDetailsService;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
String jwt = getJwtFromRequest(request);
if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
Long userId = tokenProvider.getUserIdFromJWT(jwt);
UserDetails userDetails = customUserDetailsService.loadUserById(userId);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception ex) {
log.error("Could not set user authentication in security context", ex);
}
filterChain.doFilter(request, response);
}
private String getJwtFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7, bearerToken.length());
}
return null;
}
}
PhoneJwtTokenFilter
#Slf4j
public class PhoneJwtAuthenticationFilter extends OncePerRequestFilter {
#Autowired
private JwtTokenProvider tokenProvider;
#Autowired
private CustomUserDetailsService customUserDetailsService;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
String jwt = getJwtFromRequest(request);
if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
Long userId = tokenProvider.getUserIdFromJWT(jwt);
UserDetails userDetails = customUserDetailsService.loadUserById(userId);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception ex) {
log.error("Could not set user authentication in security context", ex);
}
filterChain.doFilter(request, response);
}
private String getJwtFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7, bearerToken.length());
}
return null;
}
}
AuthenticationController
#RequestMapping(value = "/authenticate", method = RequestMethod.POST)
public ResponseEntity<?> createAuthenticationToken(#RequestBody JwtRequest authenticationRequest) throws Exception {
authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
final UserDetails userDetails = userDetailsService
.loadUserByUsername(authenticationRequest.getUsername());
final String token = jwtTokenUtil.generateToken(userDetails);
return ResponseEntity.ok(new JwtResponse(token));
}
#RequestMapping(value = "/register", method = RequestMethod.POST)
public ResponseEntity<?> saveUser(#RequestBody UserDTO user) throws Exception {
return ResponseEntity.ok(userDetailsService.save(user));
}
private void authenticate(String username, String password) throws Exception {
try {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
} catch (DisabledException e) {
throw new Exception("USER_DISABLED", e);
} catch (BadCredentialsException e) {
throw new Exception("INVALID_CREDENTIALS", e);
}
}
I have a question. I need create two class OnePerRequestFilter for email and phone or only one request for both OnePerRequestFilter? . Why spring thow exception authenticationManagerBean ? . And how to impement user login two way : one way in email and one way in phone in same spring security . Thanks you
Related
I'm new in spring boot and trying to create authentication app, but after writing it i found an error in postman app :
"timestamp": "2022-06-18T06:42:20.072+00:00",
"status": 403,
"error": "Forbidden"
This is my code :
i have 2 classes, one in Auth request and another is Auth response and I have a controller for both of them, this is the AuthRequest model:
public class AuthRequest {
#Email #Length(max = 50 , min = 5)
private String email;
#Length(max = 50 , min = 2)
private String password;
public AuthRequest(String email ,String password) {
this.email = email;
this.password = password;
}
and this is my Auth response model :
public class AuthResponse {
private String email ;
private String accessToken;
public AuthResponse(){}
public AuthResponse(String email, String accessToken) {
this.email = email;
this.accessToken = accessToken;
}
This Auth Request controller:
#RestController
public class AuthController {
#Autowired
AuthenticationManager authenticationManager;
#PostMapping("/login")
public ResponseEntity<?> login(#RequestBody #Valid AuthRequest authRequest){
try {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(authRequest.getEmail(),authRequest.getPassword()));
Employee employee = (Employee) authentication.getPrincipal();
String accessToken = "JWT access token here";
AuthResponse authResponse = new AuthResponse(employee.getEmail(), accessToken);
return ResponseEntity.ok(authResponse);
}catch (BadCredentialsException ex) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
}
}
and this is my **config security class** :
#EnableWebSecurity
#CrossOrigin
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private IEmployeeRepository employeeRepository;
#Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(username -> employeeRepository.findByEmail(username)
.orElseThrow(() -> new UsernameNotFoundException("User " + username + " not found. ")));
}
#Bean
public AuthenticationManager AuthenticationManagerBean() throws Exception {
return super.authenticationManager();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.authorizeRequests().anyRequest().permitAll();
}
}
and then, I post it to postman and this gave an error message and do not confirm :
Trace :
and i save it in the database :
The property isDeleted of the Employee object returned by the call to employeeRepository.findByEmail(username) is null and it's defined as not nullable (boolean).
Either return a not null value or change the property's type to Boolean.
I can't seem to find the 403 error problem, when I send a JSON request to register in Postman I get the 403 Forbidden error with no message.
Postman Screenshot
SecureConfig.Java
This is the configuration file.
#EnableWebSecurity
#AllArgsConstructor
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final UserDetailsService userDetailsService;
#Bean(BeanIds.AUTHENTICATION_MANAGER)
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
public void configure(HttpSecurity httpSecurity) throws Exception{
httpSecurity.csrf()
.disable()
.authorizeRequests()
.antMatchers("/api/auth/**")
.permitAll()
.anyRequest()
.authenticated();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
#Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
AuthConroller
This is the controller of the project to authenticate, when I sign up the new user was added to the database successfully, it sends afterwards a mail verification to the fake mail using Mailtrap " https://mailtrap.io " . But when I request to receive the authentication token and the username I receive the error.
#RestController
#AllArgsConstructor
#RequestMapping(value = "/api/auth", consumes = MediaType.APPLICATION_JSON_VALUE)
public class AuthController {
private final AuthService authService;
#PostMapping("/signup")
public ResponseEntity<String> signup(#RequestBody RegisterRequest registerRequest ) {
authService.signup(registerRequest);
return new ResponseEntity<>("User's Registration was successful", HttpStatus.OK);
}
#GetMapping("accountVerification/{token}")
public ResponseEntity<String> verifyAccount(#PathVariable String token){
authService.verifyAccount(token);
return new ResponseEntity<>("Account created successfully", HttpStatus.OK);
}
#PostMapping("/")
public String login(#RequestBody LoginRequest loginRequest){
return authService.login(loginRequest);
}
}
AuthService
This is the autentication service.
#Service
#AllArgsConstructor
public class AuthService {
private final PasswordEncoder passwordEncoder;
private final UserRepository userRepository;
private final VerificationTokenRepository verificationTokenRepository;
private final MailService mailService;
private final AuthenticationManager authenticationManager;
private final JwtProvider jwtProvider;
public void signup(RegisterRequest registerRequest){
User user = new User();
user.setUsername(registerRequest.getUsername());
user.setEmail(registerRequest.getEmail());
user.setPassword(passwordEncoder.encode(registerRequest.getPassword()));
user.setEnabled(false);
user.setCreated(Instant.now());
userRepository.save(user);
String token = generetedVerificationToken(user);
mailService.sendMail(new NotificationEmail("Please " +
"Activate Your Account", user.getEmail(), "Thanks " +
"for signing up. Click on the link below to go home: " +
"http://localhost:8080/api/auth/accountVerification/" + token));
}
private String generetedVerificationToken(User user){
String token = UUID.randomUUID().toString();
VerificationToken verificationToken = new VerificationToken();
verificationToken.setToken(token);
verificationToken.setUser(user);
verificationTokenRepository.save(verificationToken);
return token;
}
public void verifyAccount(String token) {
Optional<VerificationToken> verificationToken = verificationTokenRepository.findByToken(token);
fetchUserAndEnable(verificationToken.orElseThrow(() -> new SpringRedditException("Invalid Token")));
}
private void fetchUserAndEnable(VerificationToken verificationToken){
String username = verificationToken.getUser().getUsername();
Optional<org.springframework.security.core.userdetails.User> v = userRepository.findByUsername(username);
UserDetails user = userRepository.findByUsername(username).orElseThrow(() -> new SpringRedditException("User not found with name - " + username));
User s = (User) user;
s.setEnabled(true);
userRepository.save(s);
}
public String encodePassword(String password) { return passwordEncoder.encode(password);}
public String login(LoginRequest loginRequest){
Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(
loginRequest.getUsername(),
loginRequest.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
return jwtProvider.generatedToken(authentication);
}
}
JwtProvider
This class is to generate the tokens for each user. I didn't use the JKS I've found in the tutoriel, because it provides an error.
#Service
public class JwtProvider {
// private KeyStore keyStore;
private Key key;
#PostConstruct
public void init() {
key = Keys.secretKeyFor(SignatureAlgorithm.HS512);
}
public String generatedToken(Authentication authentication){
org.springframework.security.core.userdetails.User principal =
(org.springframework.security.core.userdetails.User) authentication.getPrincipal();
return Jwts.builder()
.setSubject(principal.getUsername())
.signWith(key)
.compact();
}
}
UserDetailsServiceImpl
Here goes the implementation that gives the user authority to connect :
#Service
#AllArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {
private final UserRepository userRepository;
#Override
#Transactional
public UserDetails loadUserByUsername(String username) {
Optional<User> userOptional = userRepository.findByUsername(username);
User user = userOptional.orElseThrow(() -> new UsernameNotFoundException("No such user" +
"with username : " + username));
return new org.springframework.security.core.userdetails.User(user.getUsername(),
user.getPassword(), user.isEnabled(), true,
true, true,
getAuthorities("USER"));
}
private Collection<? extends GrantedAuthority> getAuthorities(String role){
return singletonList(new SimpleGrantedAuthority(role));
}
}
and last
AutheticationResponse
In the DTO file we have this class in order to receive the authenticatonToken and the username in postman as a JSON format.
#Data
#AllArgsConstructor
#NoArgsConstructor
public class AuthenticationResponse {
private String authenticationToken;
private String username;
}
After adding logging.level.org.springframework.security=DEBUG to application.properties file, I managed to see the reason this error appears in the below image.
Debug results Screenshot
So I am working on a Spring boot project and security is one of the things I want to have in this project.
I am having this problem where my code always goes to the BadCredentialsException, but I thought my credentials are correct.
My AuthenticationController:
#RestController
#CrossOrigin
public class JwtAuthenticationController {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private JwtTokenUtil jwtTokenUtil;
#Autowired
private JwtUserDetailsService userDetailsService;
#RequestMapping(value = "/authenticate", method = RequestMethod.POST)
public ResponseEntity<?> createAuthenticationToken(#RequestBody JwtRequest authenticationRequest) throws Exception {
authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
final UserDetails userDetails = userDetailsService
.loadUserByUsername(authenticationRequest.getUsername());
final String token = jwtTokenUtil.generateToken(userDetails);
return ResponseEntity.ok(new JwtResponse(token));
}
private void authenticate(String username, String password) throws Exception {
try {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
} catch (DisabledException e) {
throw new Exception("USER_DISABLED", e);
} catch (BadCredentialsException e) {
throw new Exception("INVALID_CREDENTIALS", e);
}
}
#RequestMapping(value = "/register", method = RequestMethod.POST)
public ResponseEntity<?> saveUser(#RequestBody UserDTO user) throws Exception {
return ResponseEntity.ok(userDetailsService.save(user));
}
}
My UserDetailsService:
#Service
public class JwtUserDetailsService implements UserDetailsService {
#Autowired
private UserDao userDao;
#Autowired
private PasswordEncoder bcryptEncoder;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
DAOUser user = userDao.findByUsername(username);
if (user == null){
throw new UsernameNotFoundException("User not found with username: " + username);
}
return new User(user.getUsername(), user.getPassword(), new ArrayList<>());
}
public DAOUser save(UserDTO user) {
DAOUser newUser = new DAOUser();
newUser.setUsername(user.getUsername());
newUser.setPassword(bcryptEncoder.bCryptPasswordEncoder().encode(user.getPassword()));
return userDao.save(newUser);
}
}
My WebSecurityConfig:
#Configuration
#AllArgsConstructor
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {//provides security for endpoints
#Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
#Autowired
private UserDetailsService jwtUserDetailsService;
#Autowired
private JwtRequestFilter jwtRequestFilter;
private final AccountService accountService;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
// configure AuthenticationManager so that it knows from where to load
// user for matching credentials
// Use BCryptPasswordEncoder
auth.userDetailsService(jwtUserDetailsService).passwordEncoder(bCryptPasswordEncoder);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()//So we can send post requests without being rejected(if we using form based indication we want to enable this)
.authorizeRequests()
.antMatchers("/authenticate","/register")
.permitAll()//any request that goes trough that end point we want to allow.
.anyRequest()
.authenticated().and().exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(daoAuthenticationProvider());
}
#Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider provider =
new DaoAuthenticationProvider();
provider.setPasswordEncoder(bCryptPasswordEncoder);
provider.setUserDetailsService(accountService);
return provider;
}
}
My RequestFilter:
#Component
public class JwtRequestFilter extends OncePerRequestFilter {
#Autowired
private JwtUserDetailsService jwtUserDetailsService;
#Autowired
private JwtTokenUtil jwtTokenUtil;
#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("")){
jwt = authorizationHeader.substring(7);
username = jwtTokenUtil.extractUsername(jwt);
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null){
UserDetails userDetails = this.jwtUserDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.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);
}
}
My PasswordEncoder:
#Configuration
public class PasswordEncoder{
#Bean
public BCryptPasswordEncoder bCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
}
}
My UserDao
#Entity
#Table(name = "myusers")
public class DAOUser {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column
private String username;
#Column
#JsonIgnore
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
My UserDao Interface
I know the name is confusing, but I just followed the tutorial and he gave it this name, I have not changed it, because I want it to work before I rename files.
#Repository
public interface UserDao extends CrudRepository<DAOUser, Integer> {
DAOUser findByUsername(String username);
}
I think these are all the files you need to help me. if you need more, just ask and I will upload them.
Can anyone help me with this problem?
Thanks!!
if you read the spring documentation on passwords (which you should of done before you ask here)
you will see that the format when storing a password is:
{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
While when you are storing it you are just storing it and not adding the {bcrypt} prefix to the generated string before you store it.
newUser.setPassword(bcryptEncoder.bCryptPasswordEncoder().encode(user.getPassword()));
So when you are later providing a password, you are getting bad password since spring does not know what encoder to use when decoding the password.
In my Spring Boot MVC application I used Spring Security to provide authentication and users registration. Authentication and registration of users are working correctly, but after creating user account I want to login him automatically. While doing this I am receiving BadCredentialsException. This user with the same credentials normally is correctly login in with the login form. I appreciate any help from you. Below is my code:
Method from controller
#RequestMapping(value = "/register", method = RequestMethod.POST)
public String registerUser(#ModelAttribute("user") User user, BindingResult result,
WebRequest request, Errors errors) {
User registeredUser = null;
if (result.hasErrors() == false) {
registeredUser = createUserAccount(user, result);
}
if (registeredUser == null) {
return "/register";
}
securityService.autologin(registeredUser.getLogin(), registeredUser.getPassword());
return "/whiprounds";
}
SecurityServiceImpl (method authenticate is throwing exception)
#Service
public class SecurityServiceImpl implements SecurityService {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private UserDetailsService userDetailsService;
private static final Logger logger = LoggerFactory.getLogger(SecurityServiceImpl.class);
#Override
public String findLoggedInLogin() {
Object userDetails = SecurityContextHolder.getContext().getAuthentication().getDetails();
if (userDetails instanceof UserDetails) {
return ((UserDetails) userDetails).getUsername();
}
return null;
}
#Override
public void autologin(String username, String password) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, password, userDetails.getAuthorities());
authenticationManager.authenticate(usernamePasswordAuthenticationToken);
if (usernamePasswordAuthenticationToken.isAuthenticated()) {
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
logger.debug(String.format("Auto login %s successfully!", username));
}
}
}
UserServiceImpl
#Service
public class UserServiceImpl implements UserService {
#Autowired
private UserRepository userRepository;
#Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
#Autowired
private RoleRepository roleRepository;
#Override
public User registerNewUserAccount(User user) throws LoginExistsException {
if (loginExists(user.getLogin())) {
throw new LoginExistsException("User with this login already exists");
}
user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
user.setRoles(new HashSet<>((Collection<? extends Role>) roleRepository.findAll()));
return userRepository.save(user);
}
private boolean loginExists(String login) {
User user = userRepository.findByLogin(login);
if (user != null) {
return true;
}
return false;
}
}
UserDetailsServiceImpl
#Service
public class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
private UserRepository userRepository;
#Override
#Transactional(readOnly = true)
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
User user = userRepository.findByLogin(s);
if (user == null) {
throw new UsernameNotFoundException(s);
}
Set<GrantedAuthority> grantedAuthorities = new HashSet<>();
for (Role role : user.getRoles()) {
grantedAuthorities.add(new SimpleGrantedAuthority(role.getName()));
}
return new org.springframework.security.core.userdetails.User(user.getLogin(), user.getPassword(), grantedAuthorities);
}
}
First of all, this method throwing an exception because of the password problem (As far as I can see from your code). Your method registerNewUserAccount returning User object which password has already been hashed. Then you passing it here:
securityService.autologin(registeredUser.getLogin(), registeredUser.getPassword());
So it turns out that you're passing hashed password into authenticationManager later. This is wrong - you should pass original password into it. Smth like this:
#RequestMapping(value = "/register", method = RequestMethod.POST)
public String registerUser(#ModelAttribute("user") User user, BindingResult result,
WebRequest request, Errors errors) {
User registeredUser = null;
String originalPassword = user.getPassword();
if (result.hasErrors() == false) {
registeredUser = createUserAccount(user, result);
}
if (registeredUser == null) {
return "/register";
}
securityService.autologin(registeredUser.getLogin(), originalPassword);
return "/whiprounds";
}
Second of all, authenticationManager.authenticate(usernamePasswordAuthenticationToken); - this method actually returns filled Authentication object (if authentication was successful), and you should put this object into SecurityContext, and not the one you've passed to `authenticationManager'. Smth like this:
#Override
public void autologin(String username, String password) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, password, userDetails.getAuthorities());
Authentication auth = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
if (auth.isAuthenticated()) {
SecurityContextHolder.getContext().setAuthentication(auth);
logger.debug(String.format("Auto login %s successfully!", username));
}
}
Hope this helps.
I have a Spring 4 MVC web app which uses JPA repositories and Hibernate to map objects to a MySQL database.
I have to configure Spring Security via java class. Authentication from main page works correctly and redirect me to /admin or /user pages.
But I have a trouble: when i type in browser localhost:8080/admin - I go to the admin page, even if it is not to enter username and password.
Please show me to my mistake in config and/or in controller
my SecurityConfig.java
#Configuration
#EnableWebSecurity
#ComponentScan("com.easygoapp.service")
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("customUserDetailsService")
private UserDetailsService customUserDetailService;
//It's autowired ! Idea not correct views this!
#Autowired
private AuthenticationManagerBuilder auth;
#Autowired
AuthenticationSuccessHandler successHandler;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// CSRF protection
http.csrf()
.disable()
// requests rules
.authorizeRequests()
.antMatchers("/assets/**", "/**").permitAll()
.anyRequest().permitAll()
.and();
http.formLogin()
// login form page
.loginPage("/")
.loginProcessingUrl("/j_spring_security_check")
// URL login not success
.successHandler(successHandler)
.failureUrl("/login?error")
.usernameParameter("j_username")
.passwordParameter("j_password")
// add permissions to login page to all
.permitAll();
http.logout()
// add permissions to logout to all
.permitAll()
// logout URL
.logoutUrl("/logout")
// logout successful URL
.logoutSuccessUrl("/login?logout")
// invalidation of current session
.invalidateHttpSession(true);
http.authorizeRequests()
.antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
.antMatchers("/user/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER')")
.and().formLogin();
}
#Bean
#Override
public AuthenticationManager authenticationManager() throws Exception {
auth.userDetailsService(customUserDetailService);
return auth.build();
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public AuthenticationSuccessHandler successHandler() {
return new CustomSuccessHandler();
}
My LoginController.java
#Controller
public class LoginController {
#Autowired
BCryptPasswordEncoder encoder;
#Autowired
UserService userService;
#RequestMapping(value = {"/", "/login"}, method = RequestMethod.GET)
public ModelAndView logged() {
ModelAndView model = new ModelAndView();
model.setViewName("index");
return model;
}
#PreAuthorize("hasRole('ROLE_ADMIN')")
#RequestMapping(value = "/admin**", method = RequestMethod.GET)
public ModelAndView adminPage() {
ModelAndView model = new ModelAndView();
model.setViewName("admin");
return model;
}
#PreAuthorize("hasRole('ROLE_USER')")
#RequestMapping(value = "/user**", method = RequestMethod.GET)
public ModelAndView dbaPage() {
ModelAndView model = new ModelAndView();
model.addObject("title", "Spring Security Hello World");
model.addObject("message", "This is protected page - USER Page!");
model.setViewName("message");
return model;
}
//for 403 access denied page
#RequestMapping(value = "/403", method = RequestMethod.GET)
public ModelAndView accesssDenied() {
ModelAndView model = new ModelAndView();
//check if user is login
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (!(auth instanceof AnonymousAuthenticationToken)) {
UserDetails userDetail = (UserDetails) auth.getPrincipal();
model.addObject("username", userDetail.getUsername());
}
model.setViewName("403");
return model;
}
My CustomUserDetailService.java
#Component("customUserDetailsService")
public class CustomUserDetailService implements UserDetailsService {
#Autowired
private UserService userService;
#Override
#Transactional(readOnly = true)
public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException {
//our domain entity User
User userToAuthorise = userService.getByLogin(login);
List<GrantedAuthority> authorities = buildUserAuthority(userToAuthorise.getUserRoles());
return new org.springframework.security.core.userdetails.User(userToAuthorise.getLogin(),
userToAuthorise.getPassword(), authorities);
}
private List<GrantedAuthority> buildUserAuthority(List<UserRole> userRoles) {
List<SimpleGrantedAuthority> listAuth = new ArrayList<>();
// Build user's authorities
for (UserRole userRole : userRoles) {
listAuth.add(new SimpleGrantedAuthority(userRole.getRole()));
}
return new ArrayList<GrantedAuthority>(listAuth);
}
My CustomSuccessHandler.java
public class CustomSuccessHandler implements AuthenticationSuccessHandler {
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
#Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Authentication authentication) throws IOException, ServletException {
String targetUrl = getSuccessUrl(authentication);
redirectStrategy.sendRedirect(httpServletRequest, httpServletResponse, targetUrl);
clearAuthenticationAttributes(httpServletRequest);
}
private String getSuccessUrl(final Authentication authentication) {
boolean isUser = false;
boolean isAdmin = false;
final Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for (final GrantedAuthority grantedAuthority : authorities) {
if (grantedAuthority.getAuthority().equals("ROLE_USER")) {
isUser = true;
break;
} else if (grantedAuthority.getAuthority().equals("ROLE_ADMIN")) {
isAdmin = true;
break;
}
}
if (isUser) {
return "/user";
} else if (isAdmin) {
return "/admin";
} else {
throw new IllegalStateException();
}
}
protected final void clearAuthenticationAttributes(final HttpServletRequest request) {
final HttpSession session = request.getSession(false);
if (session == null) {
return;
}
session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
}
Thank for Your answers!