Java Spring安全性:在UserDetailsService方法中获取密码

Java Spring安全性:在UserDetailsService方法中获取密码,java,spring,spring-security,Java,Spring,Spring Security,为了获得我的帐户,我需要登录一个外部spring应用程序。为什么需要它并不重要,但为了在API上执行/login调用,我需要在UserDetailsService方法中获取密码。这是我的安全设置: //https://auth0.com/blog/implementing-jwt-authentication-on-spring-boot/ @EnableWebSecurity public class WebSecurity extends WebSecurityConfigurerAdapt

为了获得我的帐户,我需要登录一个外部spring应用程序。为什么需要它并不重要,但为了在API上执行/login调用,我需要在UserDetailsService方法中获取密码。这是我的安全设置:

//https://auth0.com/blog/implementing-jwt-authentication-on-spring-boot/
@EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
    private UserDetailsService userDetailsService;
    private BCryptPasswordEncoder bCryptPasswordEncoder;

//Constructor gets authLogic for external authentication
@Autowired
public WebSecurity(@Qualifier("authLogic") UserDetailsService userDetailsService){
    this.userDetailsService = userDetailsService;
    this.bCryptPasswordEncoder = new BCryptPasswordEncoder();
}

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.cors().and().csrf().disable()
            .authorizeRequests()
            .antMatchers("/v2/api-docs", "/configuration/ui", "/swagger-resources", "/configuration/security", "/swagger-ui.html", "/webjars/**", "/swagger-resources/configuration/ui", "/swagger-resources/configuration/security").permitAll()
            .anyRequest().authenticated()
            .and()
            .addFilter(new JwtAuthenticationFilter(authenticationManager()))
            .addFilter(new JwtAuthorizationFilter(authenticationManager()))
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}

@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
}

@Bean
public CorsConfigurationSource corsConfigurationSource() {
    final CorsConfiguration configuration = new CorsConfiguration();
    configuration.setAllowedOrigins(Arrays.asList(BANK_API, INVENTORY_API, MARKET_API)); //TODO: is dit correct??
    configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "PATCH"));
    configuration.setAllowCredentials(true);
    configuration.setAllowedHeaders(Arrays.asList("*"));
    configuration.setExposedHeaders(Arrays.asList("X-Auth-Token","Authorization","Access-Control-Allow-Origin","Access-Control-Allow-Credentials"));

    final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", configuration);
    return source;
}
}
My UserDetails服务方法实现:

@Service
public class AuthLogic implements UserDetailsService {
    private HttpServletRequest request;
    private IAccountRepository accountRepository;
    private RestCallLogic restCall;

    @Autowired
    public AuthLogic(HttpServletRequest request, IAccountRepository accountRepository, RestCallLogic restCall){
        this.request = request;
        this.accountRepository = accountRepository;
        this.restCall = restCall;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //get password
        //make restcall to external login
    }
}
在使用spring安全性实现时,有没有办法获取密码。因为我可以很容易地创建自己的类并从那里进行登录,但最好使用Spring安全性。此外,登录还返回一个令牌,我可以将其转换为用户。也许我只是想得太多了

为了进行API调用,我需要编写一个自定义AuthenticationProvider:

@Component
public class JwtAuthenticationProvider implements AuthenticationProvider {

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

        String username = authentication.getName();
        String password = authentication.getCredentials().toString();

        UserDetails principal = new User(username, password, new ArrayList<>());

        return new UsernamePasswordAuthenticationToken(principal, password, new ArrayList<>());
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }
}
@组件
公共类JwtAuthenticationProvider实现AuthenticationProvider{
@凌驾
公共身份验证(身份验证)引发AuthenticationException{
字符串username=authentication.getName();
字符串密码=authentication.getCredentials().toString();
UserDetails principal=新用户(用户名、密码、新ArrayList());
返回新的UsernamePasswordAuthenticationToken(主体、密码、新ArrayList());
}
@凌驾
公共布尔支持(类身份验证){
返回authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}

在幕后,Spring Security在筛选器中解析用户凭据(例如,
BasicAuthenticationFilter
UsernamePasswordAuthenticationFilter
等-筛选器检索用户凭据),如果此筛选器成功检索到用户的凭据,它会将此类凭据传递给
AuthenticationProvider
,以验证凭据并创建用户详细信息()。
AuthenticationProvider
可以通过各种方式验证凭据

AuthenticationProvider
的一个实现是
DaoAuthenticationProvider
,它尝试在
UserDetails服务
中按用户名查找用户,如果找到,则从
UserDetails服务
获取用户的
UserDetails
,然后检查用户提供的密码是否符合要求
UserDetails
中的密码

在您的情况下,您需要在
UserDetailsService
中提出此类请求,而不是在
AuthenticationProvider
中,因为它对此类情况负责

我的建议是从spring security扩展
AbstractUserDetailsAuthenticationProvider
类,并在抽象方法
protected AbstractUserDetails retrieveUser(String username,UsernamePasswordAuthenticationTokenAuthentication)中实现您的功能抛出AuthenticationException

例如:

@Configuration
public class WebSecurityConf43547 extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(new AbstractUserDetailsAuthenticationProvider() {
            @Override
            protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken) throws AuthenticationException {
                //from docs: "[...]Generally a subclass will at least compare the 
                //Authentication.getCredentials() with a UserDetails.getPassword() [...]"
            }

            @Override
            protected UserDetails retrieveUser(String s, UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken) throws AuthenticationException {
                usernamePasswordAuthenticationToken.getCredentials();
                //your api here
            }
        });
    }
}

更好的例子是:看看
DaoAuthenticationProvider
如何在SpringSecurity中扩展
AbstractUserDetailsAuthenticationProvider

一周后,我终于得到了想要的。因此,我创建了一个自定义身份验证提供程序,它将对我的身份验证API进行REST调用。如果我提供的用户名和密码正确,我将获得一个包含用户名、角色和ID的JWT令牌。之后,我只需调用一个自定义身份验证服务,检查其数据库中是否已经存在该用户ID。如果不是这样,我将使用JWT令牌中的给定id创建一个新用户

这是我的自定义身份验证提供程序:

public class JwtAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {

    //custom authentication service
    private AuthLogic userDetailsImpl;

    public JwtAuthenticationProvider(AuthLogic userDetailsImpl) {
        this.userDetailsImpl = userDetailsImpl;
    }

    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        //JWTUser is a custom class that extends the UserDetails class from spring
        JwtUser user = (JwtUser) userDetails;

        //call the custom auth service to check if the user exists in the database
        userDetailsImpl.loadUserByUsername(user.getUserID(), user.getUsername());
    }

    @Override
    protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        //get the token from a external authentication API
        String token = retrieveAccountData(new LoginWrapper(username, authentication.getCredentials().toString()));

        Claims claims = Jwts.parser()
                .setSigningKey(JWTKEY)
                .parseClaimsJws(token)
                .getBody();

        List<String> scopes = (List<String>) claims.get("scopes");
        int UserId = (int) claims.get("userID");
        List<GrantedAuthority> authorities = scopes.stream()
                .map(authority -> new SimpleGrantedAuthority(authority))
                .collect(Collectors.toList());

        //return the User
        return new JwtUser(UserId, username, authentication.getCredentials().toString(), authorities);
    }

    private String retrieveAccountData(LoginWrapper loginWrapper){
        URI uri = UriComponentsBuilder.fromUriString(BANK_LOGIN).build().toUri();
        Gson gson = new GsonBuilder().create();

        RequestEntity<String> request = RequestEntity
                .post(uri)
                .accept(MediaType.APPLICATION_JSON)
                .body(gson.toJson(loginWrapper));

        //post call
        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<String> response = restTemplate.exchange(request, String.class);

        //check if status code is correct
        if(response.getStatusCode() != HttpStatus.OK) {
            throw new UsernameNotFoundException(loginWrapper.getUsername());
        }

        //convert to LoginWrapper
        return gson.fromJson(response.getBody(), TokenWrapper.class).getToken();
    }
}

我希望这个答案有帮助。

不,你不知道。。。您需要一个自定义的
AuthenticationProvider
而不是自定义的
UserDetailsService
。你试图在错误的地方解决它。谢谢你的有用信息!!!我知道的唯一问题是,我的自定义AuthenticationProvider在我执行/Login操作时不会触发,但您的
AuthenticationProvider
没有调用任何东西?它只返回一个用户,根本不调用外部系统。另外,我怀疑您的
JwtAuthenticationFilter
是否实际生成了
UsernamePasswordAuthentication
。我是否必须在WebSecurity配置适配器中进行任何其他配置。因为我开始创建一个自定义AuthenticationProvider类,当我登录时它不会被触发,它只是说拒绝访问。还有一个问题:我应该在哪里调用数据库来检查本地数据库中是否存在该帐户?
@Service
public class AuthLogic {
    private IAccountRepository accountRepository;

    @Autowired
    public AuthLogic(IAccountRepository context) {
        this.accountRepository = context;
    }
trough with the jwt token)
    public UserDetails loadUserByUsername(int userId, String username) throws UsernameNotFoundException {
        Optional<Account> foundAccount = accountRepository.findById(userId);

        Account account;
        //check if user has logged in to our inventory API before, if not create new account
        if (!foundAccount.isPresent()) {
            account = accountRepository.save(new Account(userId, username));
        } else {
            account = foundAccount.get();
        }

        return new JwtUserPrincipal(account);
    }
}
@EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
    private JwtAuthenticationProvider authenticationProvider;

    @Autowired
    public WebSecurity(@Qualifier("authLogic") AuthLogic userDetailsImpl) {
        this.authenticationProvider = new JwtAuthenticationProvider(userDetailsImpl);
    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(authenticationProvider);
    }
}