Spring

스프링 / authenticate 토큰 생성기 끝점에서 유효하지만 잘못된 자격 증명

기록만이살길 2021. 3. 21. 03:07
반응형

스프링 / authenticate 토큰 생성기 끝점에서 유효하지만 잘못된 자격 증명

1. 질문(문제점):

본문에 제출 된 사용자 / 암호가 유효한 경우 토큰을 생성하기 위해 Spring에서 처리 된 경로를 구축하려고합니다. 해당 시나리오에서만 토큰으로 응답합니다.

문제는 정확히 DB에 저장된대로 올바른 사용자 이름과 암호를 게시하고 있지만 "잘못된 자격 증명"오류가 계속 발생한다는 것입니다. 이것이 컨트롤러입니다.

@RequestMapping(value = "/authenticate", method = RequestMethod.POST)
public ResponseEntity<?> createAuthenticationToken(@RequestBody JwtRequest authenticationRequest) throws Exception {
    try {
        LOGGER.info("Received a request to generate a token for user: "+authenticationRequest.getUsername()+"/"+authenticationRequest.getPassword());

        final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
        System.out.println(userDetails);
        // Logs: org.springframework.security.core.userdetails.User@7ee29d27: Username: thisWasTheGoodUserName; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: Admin
        // And also logs the hibernate query:
        //    select
        //        employee0_.id as id1_0_,
        //        employee0_.employee_name as employee_name2_0_,
        //        employee0_.pwd as pwd3_0_,
        //        employee0_.user_name as user_nam4_0_ 
        //    from
        //        employees employee0_ 
        //    where
        //        employee0_.user_name=?

        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, authenticationRequest.getPassword(), userDetails.getAuthorities());
        // This step gets executed

        authenticationManager.authenticate(usernamePasswordAuthenticationToken);
        // And the same query to the DB is logged, one that I can run on dbeaver manually and get results from:
        //    select
        //        employee0_.id as id1_0_,
        //        employee0_.employee_name as employee_name2_0_,
        //        employee0_.pwd as pwd3_0_,
        //        employee0_.user_name as user_nam4_0_ 
        //    from
        //        employees employee0_ 
        //    where
        //        employee0_.user_name=?
        // And throws exception in this authenticate
        
        final String token = jwtTokenUtil.generateToken(userDetails);

        TokenSucess tokenSuccessResponse = new TokenSucess(true, new JwtResponse(token));
        LOGGER.info("Obtained token with success ");
        return new ResponseEntity<TokenSucess>(tokenSuccessResponse, HttpStatus.OK);
    } catch (Exception e) {
        TokenError tokenErrorResp = new TokenError(false, "Error generating token.");
        LOGGER.error("Error generating a token. Details: "+ e);
        return new ResponseEntity<TokenError>(tokenErrorResp, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

이것은 내가 서비스에서 사용하는 방법입니다.

@Service
public class JwtUserDetailsService implements UserDetailsService {
    @Autowired
    private EmployeeRepository employeeRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Employee emp = employeeRepository.findByUserName(username);
        if (emp == null ) {
            throw new UsernameNotFoundException("Employee not found with username: " + username);
        }
        List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
        authorities.add(new SimpleGrantedAuthority("Admin"));

        return new org.springframework.security.core.userdetails.User(emp.getUserName(), emp.getPassword(), authorities);
    }
}

Security 구성 :

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

    @Autowired
    private UserDetailsService jwtUserDetailsService;

    @Autowired
    private JwtRequestFilter jwtRequestFilter;

    private static final Logger LOGGER = LoggerFactory.getLogger(WebSecurityConfig.class);

    @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 {
        try {
            httpSecurity.csrf().disable()
                    // don't authenticate this particular request
                    .authorizeRequests().antMatchers("/authenticate").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);
        } catch (Exception e) {
            LOGGER.error("Error handling http security configs. Details: "+e.getMessage());
        }
    }
}

그리고 이것은 컬의 예입니다.

curl --location --request POST 'http://127.0.0.1:<port>/authenticate' \
--header 'Content-Type: application/json' \
--data-raw '{
    "username": "thisWasTheGoodUserName",
    "password": "123454321"
}'

이미 DB에서 다른 시나리오로 시도했습니다.

1. thisWasTheGoodUserName/123454321
2. thisWasTheGoodUserName/$2y$12$Mj0PRHipe14Wgm5c/GOuO.RyhjhuwRwoQYUnK8LcgsvHzQ4weYHGm (bcrypted 123454321)

여러 "단계 진입"을 사용 /Users/<user>/.m2/repository/org/springframework/security/spring-security-core/5.0.3.RELEASE/spring-security-core-5.0.3.RELEASE.jar!/org/springframework/security/authentication/dao/DaoAuthenticationProvider.class하여 암호가 동일한 문자열 인 경우에도 다음 함수에서 문제가 발생하는 것을 발견했습니다 .

protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
    if (authentication.getCredentials() == null) {
        this.logger.debug("Authentication failed: no credentials provided");
        throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
    } else {
        String presentedPassword = authentication.getCredentials().toString();

        // presentedPassword is exactly the same as the one in userDetails.getPassword() but the matcher returns False...

        if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
            this.logger.debug("Authentication failed: password does not match stored value");
            throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        }
    }
}

내가 뭘 잘못하고 다음 예외를 일으킬 수 있는지 아십니까?

ERROR c.r.t.s.c.JwtAuthenticationController - Error generating a token. Details: org.springframework.security.authentication.BadCredentialsException: Bad credentials

참고 :이 모든 원인이되는 문제를 발견했습니다. DB에 저장된 비밀번호가 온라인 bcrypt 생성기를 사용하여 암호화되었습니다 ... 값이 동일한 문자열이지만 생성 된 bcrypt 인코딩 된 비밀번호와 일치하지 않습니다.

2. 해결방안:

AuthenticationProvider를 통해 사용자 지정 인증을 수행 할 수 있습니다. configureGlobal ()을 어노테이션 처리해야합니다. 다음은 WebSecurityConfig의 샘플입니다.



@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

   // @Autowired
   // private UserDetailsService jwtUserDetailsService;

    @Autowired
    private JwtRequestFilter jwtRequestFilter;

    private static final Logger LOGGER = LoggerFactory.getLogger(WebSecurityConfig.class);

    //@Autowired
    //public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    //    auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder());
    //}

    //@Bean
    //public PasswordEncoder passwordEncoder() {
    //    return new BCryptPasswordEncoder();
    //}

    @Autowired
    private HttpServletRequest request;

    @Autowired
    private EmployeeRepository employeeRepository;

    @Bean
    protected AuthenticationProvider authenticationProvider(){


        return new AuthenticationProvider(){

            @Override
            public Authentication authenticate(Authentication auth) throws AuthenticationException {

            
                String username= request.getParameter("username");
                String password = request.getParameter("password");


                Employee user= employeeRepository.findByUserName(username);
                if (emp == null ) {
                  throw new UsernameNotFoundException("Employee not found with username: " 
                  + username);
                }

                // validate pwd by yourself. 
                // you should use same bcrypt generator to validate when saving pwd in DB
                if(!UserUtils.validatePassword(password, user.getPassword())){
                    throw new BadCredentialsException("wrong password!");
                }
                

                List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
                authorities.add(new SimpleGrantedAuthority("Admin"));               

                return new UsernamePasswordAuthenticationToken(username,
                        null, authorities);

            }

            @Override
            public boolean supports(Class<?> authentication) {
                return true;
            }

        };
    }



    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        try {
            httpSecurity.csrf().disable()
                    // don't authenticate this particular request
                    .authorizeRequests().antMatchers("/authenticate").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);
        } catch (Exception e) {
            LOGGER.error("Error handling http security configs. Details: "+e.getMessage());
        }
    }
}


65659610
반응형