一、系统
注意部署在同一机器(localhost)上的三个应用,为了防止存放在cookie中的JSESSIONID不被覆盖,需要设置不同的path,可以在配置文件中指定不同的上下文路径,如:servlet: context-path: /crm、servlet: context-path: /oa
认证中心系统OAUTHSERVER http://localhost:8888
客户端系统1CRM http://localhost:8090/crm
客户端系统2OA http://localhost:8080/oa
二、过滤器链执行流程
2、重定向http://localhost:8080/oa/login
3、重定向到认证中心请求授权码http://localhost:8888/oauth/authorize?client_id=oa&redirect_uri=http://localhost:8080/oa/login&response_type=code&state=BeSEN2
4、重定向到认证中心的登录处理逻辑http://localhost:8888/login
5、提交登录请求http://localhost:8888/login
关键代码
UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
return;
}
if (logger.isDebugEnabled()) {
logger.debug("Request is to process authentication");
}
Authentication authResult;
try {
// 尝试对用户输入信息进行认证
authResult = attemptAuthentication(request, response);
if (authResult == null) {
// return immediately as subclass has indicated that it hasn't completed
// authentication
return;
}
// session处理,调用AbstractSessionFixationProtectionStrategy.onAuthentication
// 更改sessionId
sessionStrategy.onAuthentication(authResult, request, response);
}
catch (InternalAuthenticationServiceException failed) {
logger.error(
"An internal error occurred while trying to authenticate the user.",
failed);
unsuccessfulAuthentication(request, response, failed);
return;
}
catch (AuthenticationException failed) {
// Authentication failed
unsuccessfulAuthentication(request, response, failed);
return;
}
// Authentication success
if (continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
// 将认证结果存放到SecurityContext,并进行重定向(返回码302,认证中心重定向到授权链接)
successfulAuthentication(request, response, chain, authResult);
}
7、重定向到OA的登录服务http://localhost:8080/oa/login?code=3XDwIM&state=BeSEN2
AbstractAuthenticationProcessingFilter的核心代码
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
return;
}
if (logger.isDebugEnabled()) {
logger.debug("Request is to process authentication");
}
Authentication authResult;
try {
// 从认证中心获取认证结果
authResult = attemptAuthentication(request, response);
if (authResult == null) {
// return immediately as subclass has indicated that it hasn't completed
// authentication
return;
}
// sessionId重新生成
sessionStrategy.onAuthentication(authResult, request, response);
}
catch (InternalAuthenticationServiceException failed) {
logger.error(
"An internal error occurred while trying to authenticate the user.",
failed);
unsuccessfulAuthentication(request, response, failed);
return;
}
catch (AuthenticationException failed) {
// Authentication failed
unsuccessfulAuthentication(request, response, failed);
return;
}
// Authentication success
if (continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
// 认证结果处理
successfulAuthentication(request, response, chain, authResult);
}
OAuth2ClientAuthenticationProcessingFilter的核心代码
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
OAuth2AccessToken accessToken;
try {
accessToken = restTemplate.getAccessToken();
} catch (OAuth2Exception e) {
BadCredentialsException bad = new BadCredentialsException("Could not obtain access token", e);
publish(new OAuth2AuthenticationFailureEvent(bad));
throw bad;
}
try {
OAuth2Authentication result = tokenServices.loadAuthentication(accessToken.getValue());
if (authenticationDetailsSource!=null) {
request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, accessToken.getValue());
request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE, accessToken.getTokenType());
result.setDetails(authenticationDetailsSource.buildDetails(request));
}
publish(new AuthenticationSuccessEvent(result));
return result;
}
catch (InvalidTokenException e) {
BadCredentialsException bad = new BadCredentialsException("Could not obtain user details from token", e);
publish(new OAuth2AuthenticationFailureEvent(bad));
throw bad;
}
}
认证中心的BasicAuthenticationFilter的核心代码
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
final boolean debug = this.logger.isDebugEnabled();
String header = request.getHeader("Authorization");
if (header == null || !header.toLowerCase().startsWith("basic ")) {
chain.doFilter(request, response);
return;
}
try {
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));
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);
}
8、重定向到http://localhost:8080/oa/
9、访问crm系统不需要再次登录http://localhost:8090/crm/system/profile
浏览器自动(响应码302)访问http://localhost:8090/crm/login?code=2P7uPo&state=Io2Ize
10、退出流程
在WebSecurityConfigurerAdapter的子类中配置HttpSecurity,指定本地退出成功后需要重定向的地址,如下:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.logout().logoutSuccessUrl(认证中心的logout服务地址)
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.csrf().disable();
}