Recent SpringBoot2.0 the new building project, jar many packages dependent on the structure of the new version of the relevant SpringBoot made changes, its dependencies, there are many different, I am responsible for the company's basic services related to login authentication, certification resources using open source Spring-Security-Oauth2
to build, but Many pits will be encountered during the construction process, so this record is made.
Pit one:
The security dependency referenced by Spring boot 2.0.X is spring security 5.X version, this version needs to provide an PasswordEncorder
instance, otherwise the background report error:
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
Annotation exposes a PasswordEncorder instance
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
Pit two:
Mining memory configuration clientId
and secret
at the request {{url}}/oauth/token
acquiring token Interface:
WARN [http-nio-8020-exec-2 ] o.s.s.c.b.BCryptPasswordEncoder:90 - [ ] Encoded password does not look like BCrypt
See verified by first breakpoint debug is configured clientId
and secret
authentication filter is BasicAuthenticationFilter
understood the filter head is reading the main source configuration header Authorization : Basic XXXX
header information to authenticate . The core code is:
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
final boolean debug = this.logger.isDebugEnabled();
//获取头部信息Authorization的basic认证信息
String header = request.getHeader("Authorization");
if (header == null || !header.toLowerCase().startsWith("basic ")) {
chain.doFilter(request, response);
return;
}
try {
//获取头部信息Authorization的basic认证信息(尽心base64解码)
String[] tokens = extractAndDecodeHeader(header, request);
assert tokens.length == 2;
String username = tokens[0];
if (debug) {
this.logger
.debug("Basic Authentication Authorization header found for user '"
+ username + "'");
}
if (authenticationIsRequired(username)) {
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, tokens[1]);
authRequest.setDetails(
this.authenticationDetailsSource.buildDetails(request));
//认证头部信息,通过authenticationManager选择合适的provider尽心认证,失败则抛出异常AuthenticationException
Authentication authResult = this.authenticationManager
.authenticate(authRequest);
if (debug) {
this.logger.debug("Authentication success: " + authResult);
}
SecurityContextHolder.getContext().setAuthentication(authResult);
this.rememberMeServices.loginSuccess(request, response, authResult);
onSuccessfulAuthentication(request, response, authResult);
}
}
//根据抛出的异常信息,做不同处理
catch (AuthenticationException failed) {
SecurityContextHolder.clearContext();
if (debug) {
this.logger.debug("Authentication request for failed: " + failed);
}
this.rememberMeServices.loginFail(request, response);
onUnsuccessfulAuthentication(request, response, failed);
if (this.ignoreFailure) {
chain.doFilter(request, response);
}
else {
this.authenticationEntryPoint.commence(request, response, failed);
}
return;
}
chain.doFilter(request, response);
}
Basic authentication authenticationManager
uses the authentication method of the DaoAuthenticationProvider
middle parent abstract class AbstractUserDetailsAuthenticationProvider
to authenticate
obtain basic authentication information. The main authentication core code is:
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.onlySupports",
"Only UsernamePasswordAuthenticationToken is supported"));
// Determine username
String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
: authentication.getName();
boolean cacheWasUsed = true;
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
cacheWasUsed = false;
try {
//获取basic认证用户信息即根据clientId获取ClientDetails信息(secret,scope,authorizedGrantTypes.....)
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
}
catch (UsernameNotFoundException notFound) {
logger.debug("User '" + username + "' not found");
if (hideUserNotFoundExceptions) {
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
else {
throw notFound;
}
}
Assert.notNull(user,
"retrieveUser returned null - a violation of the interface contract");
}
try {
//检查是否被lock,是否过期等
preAuthenticationChecks.check(user);
//检查clientId和secret是否匹配
additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken) authentication);
}
catch (AuthenticationException exception) {
if (cacheWasUsed) {
// There was a problem, so try again after checking
// we're using latest data (i.e. not from the cache)
cacheWasUsed = false;
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
preAuthenticationChecks.check(user);
additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken) authentication);
}
else {
throw exception;
}
}
postAuthenticationChecks.check(user);
if (!cacheWasUsed) {
this.userCache.putUserInCache(user);
}
Object principalToReturn = user;
if (forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
return createSuccessAuthentication(principalToReturn, authentication, user);
}
The core code for detecting clientId
and secret
matching is:
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
if (authentication.getCredentials() == null) {
logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
String presentedPassword = authentication.getCredentials().toString();
//此处采用配置的passwordEncoder编码并检查secret是否匹配
if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
}
if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword()))
Whether the core authentication secret is configured.
So we configured in memory mode ClientDetailsServiceConfigurer
when
you need to secret
be passwordEncoder be encoder processing.
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient(clientId).secret(passwordEncoder.encode(secret)).authorizedGrantTypes("password", "refresh_token").scopes("read,write")
.accessTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(1)).refreshTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(7));
}
passwordEncoder.encode(secret)
get on