在前两篇博客中介绍了OAuth2和SpringSecurity的基本知识,本篇介绍OAuth2协议实现的SSO单点登录。
项目目录结构介绍
文章图片
- sso-parent 父工程
- order-service 订单微服务
- sso-auth-server sso认证服务器
- sso-gateway 微服务网关
- sso-client 客户端(第三方应用)
文章图片
认证服务器 【OAuth2|深入OAuth2 微服务下的SSO单点登录】认证服务器的配置和之前的差不多,把客户端信息存到了数据中,不在是内存里
- 项目结构
文章图片
@EnableJdbcHttpSession
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthServerConfigextends AuthorizationServerConfigurerAdapter { @Autowired
DataSource dataSource;
@Qualifier
UserDetailsService userDetailsService;
@Autowired
private AuthenticationManager authenticationManager;
@Bean
public TokenStore tokenStore () {
return new JdbcTokenStore(dataSource);
} @Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.checkTokenAccess("isAuthenticated()");
} @Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
} @Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore())
.userDetailsService(userDetailsService)
.authenticationManager(authenticationManager);
}}
@Configuration
public class OAuth2LogoutSuccessHandler implements LogoutSuccessHandler { @Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
String redirectUri = request.getParameter("redirect_uri");
if(!StringUtils.isEmpty(redirectUri)) {
response.sendRedirect(redirectUri);
}
}
}
@Configuration
public class OAuth2WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired
private UserDetailsService userDetailsService;
@Autowired
private LogoutSuccessHandler logoutSuccessHandler;
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
} @Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
} @Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
} @Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().and()
.httpBasic().and()
.logout()
.logoutSuccessHandler(logoutSuccessHandler);
}}
@Service
public class UserDetailsServiceImpl implements UserDetailsService { @Autowired
private PasswordEncoder passwordEncoder;
@Autowired
JdbcTemplate jdbcTemplate;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Map, Object> entity = jdbcTemplate
.queryForMap("SELECT * FROM SYS_USER WHERE `USERNAME` = ?", username);
if (Objects.nonNull(entity)) {
return new User((String) entity.get("username"),
(String) entity.get("password"),
AuthorityUtils.createAuthorityList("ROLE_ADMIN"));
}return null;
}}
@SpringBootApplication
public class AuthServerApplication {
public static void main(String[] args) {
SpringApplication.run(AuthServerApplication.class, args);
}
}
网关
- 代码结构
文章图片
@Slf4j
@Component
public class AuthorizationFilter extends ZuulFilter {
@Override
public String filterType() {
return "pre";
} @Override
public int filterOrder() {
return 2;
} @Override
public boolean shouldFilter() {
return true;
} @Override
public Object run() throws ZuulException {
log.info("authorization start");
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
if(isNeedAuth(request)) {TokenInfo tokenInfo = (TokenInfo)request.getAttribute("tokenInfo");
if(tokenInfo != null && tokenInfo.isActive()) {
if(!hasPermission(tokenInfo, request)) {
log.info("audit log update fail 403");
handleError(403, requestContext);
}requestContext.addZuulRequestHeader("username", tokenInfo.getUser_name());
} else {
if(!StringUtils.startsWith(request.getRequestURI(), "/token")) {
log.info("audit log update fail 401");
handleError(401, requestContext);
}
}
}return null;
} private boolean isNeedAuth(HttpServletRequest request) {
return true;
} private boolean hasPermission(TokenInfo tokenInfo, HttpServletRequest request) {
return true;
} private void handleError(int status, RequestContext requestContext) {
requestContext.getResponse().setContentType("application/json");
requestContext.setResponseStatusCode(status);
requestContext.setResponseBody("{\"message\":\"auth fail\"}");
// 这个请求最终不会被zuul转发到后端服务器
requestContext.setSendZuulResponse(false);
}
}
@Slf4j
@Component
public class OAuthFilter extends ZuulFilter { RestTemplate restTemplate = new RestTemplate();
@Override
public String filterType() {
return "pre";
} @Override
public int filterOrder() {
return 1;
} @Override
public boolean shouldFilter() {
return true;
} @Override
public Object run() throws ZuulException {log.info("oauth start");
// 为了获取request请求
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
// 获取token请求不进行过滤
if(StringUtils.startsWith(request.getRequestURI(), "/token")) {
return null;
}String authHeader = request.getHeader("Authorization");
if(StringUtils.isBlank(authHeader)) {
return null;
}if(!StringUtils.startsWithIgnoreCase(authHeader, "bearer ")) {
return null;
}// 如果请求头带了以barer开头的请求,则去认证服务器校验token
try {TokenInfo info = getTokenInfo(authHeader);
request.setAttribute("tokenInfo", info);
} catch (Exception e) {
log.error("get token info fail", e);
}return null;
} /**
* 校验token
* @param authHeader
* @return
*/
private TokenInfo getTokenInfo(String authHeader) {String token = StringUtils.substringAfter(authHeader, "bearer ");
String oauthServiceUrl = "http://localhost:9090/oauth/check_token";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.setBasicAuth("gateway", "123456");
MultiValueMap, String> params = new LinkedMultiValueMap<>();
params.add("token", token);
HttpEntity> entity = new HttpEntity<>(params, headers);
ResponseEntity response = restTemplate.exchange(oauthServiceUrl, HttpMethod.POST, entity, TokenInfo.class);
log.info("token info :" + response.getBody().toString());
return response.getBody();
}
}
客户端代码
- 项目接口
文章图片
@SpringBootApplication
@RestController
@EnableZuulProxy
@Slf4j
public class ClientApplication { private RestTemplate restTemplate = new RestTemplate();
@PostMapping("/logout")
public void logout(HttpServletRequest request, HttpServletResponse response) throws IOException {
request.getSession().invalidate();
} @GetMapping("/me")
public TokenInfo me(HttpServletRequest request) {
TokenInfo info = (TokenInfo)request.getSession().getAttribute("token");
return info;
} @GetMapping("/oauth/callback")
public void callback (@RequestParam String code, String state, HttpServletRequest request, HttpServletResponse response) throws IOException {log.info("state is "+state);
String oauthServiceUrl = "http://gateway.pipiha.com:9070/token/oauth/token";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.setBasicAuth("admin", "123456");
MultiValueMap, String> params = new LinkedMultiValueMap<>();
params.add("code", code);
params.add("grant_type", "authorization_code");
params.add("redirect_uri", "http://admin.pipiha.com:8080/oauth/callback");
HttpEntity> entity = new HttpEntity<>(params, headers);
ResponseEntity token = restTemplate.exchange(oauthServiceUrl, HttpMethod.POST, entity, TokenInfo.class);
//request.getSession().setAttribute("token", token.getBody().init());
Cookie accessTokenCookie = new Cookie("pipiha_access_token", token.getBody().getAccess_token());
accessTokenCookie.setMaxAge(token.getBody().getExpires_in().intValue());
accessTokenCookie.setDomain("pipiha.com");
accessTokenCookie.setPath("/");
response.addCookie(accessTokenCookie);
Cookie refreshTokenCookie = new Cookie("pipiha_refresh_token", token.getBody().getRefresh_token());
refreshTokenCookie.setMaxAge(2592000);
refreshTokenCookie.setDomain("pipiha.com");
refreshTokenCookie.setPath("/");
response.addCookie(refreshTokenCookie);
response.sendRedirect("/");
}
public static void main(String[] args) {
SpringApplication.run(ClientApplication.class, args);
}}
@Component
public class CookieTokenFilter extends ZuulFilter {
private RestTemplate restTemplate = new RestTemplate();
@Override
public boolean shouldFilter() {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
return !StringUtils.equals(request.getRequestURI(), "/logout");
} @Override
public Object run() throws ZuulException {
RequestContext requestContext = RequestContext.getCurrentContext();
//HttpServletRequest request = requestContext.getRequest();
HttpServletResponse response = requestContext.getResponse();
String accessToken = getCookie("pipiha_access_token");
if(StringUtils.isNotBlank(accessToken)) {
requestContext.addZuulRequestHeader("Authorization", "bearer "+accessToken);
} else {
String refreshToken = getCookie("pipiha_refresh_token");
if(StringUtils.isNotBlank(refreshToken)) {
String oauthServiceUrl = "http://gateway.pipiha.com:9070/token/oauth/token";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.setBasicAuth("admin", "123456");
MultiValueMap, String> params = new LinkedMultiValueMap<>();
params.add("grant_type", "refresh_token");
params.add("refresh_token", refreshToken);
HttpEntity> entity = new HttpEntity<>(params, headers);
try {
ResponseEntity newToken = restTemplate.exchange(oauthServiceUrl, HttpMethod.POST, entity, TokenInfo.class);
//request.getSession().setAttribute("token", newToken.getBody().init());
requestContext.addZuulRequestHeader("Authorization", "bearer "+newToken.getBody().getAccess_token());
Cookie accessTokenCookie = new Cookie("pipiha_access_token", newToken.getBody().getAccess_token());
accessTokenCookie.setMaxAge(newToken.getBody().getExpires_in().intValue());
accessTokenCookie.setDomain("pipiha.com");
accessTokenCookie.setPath("/");
response.addCookie(accessTokenCookie);
Cookie refreshTokenCookie = new Cookie("pipiha_refresh_token", newToken.getBody().getRefresh_token());
refreshTokenCookie.setMaxAge(2592000);
refreshTokenCookie.setDomain("pipiha.com");
refreshTokenCookie.setPath("/");
response.addCookie(refreshTokenCookie);
} catch (Exception e) {
requestContext.setSendZuulResponse(false);
requestContext.setResponseStatusCode(500);
requestContext.setResponseBody("{\"message\":\"refresh fail\"}");
requestContext.getResponse().setContentType("application/json");
}} else {
requestContext.setSendZuulResponse(false);
requestContext.setResponseStatusCode(500);
requestContext.setResponseBody("{\"message\":\"refresh fail\"}");
requestContext.getResponse().setContentType("application/json");
}
}return null;
} private String getCookie(String name) {
String result = null;
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
Cookie[] cookies = request.getCookies();
for (Cookie cookie : cookies) {
if(StringUtils.equals(name, cookie.getName())) {
result = cookie.getValue();
break;
}
}return result;
} @Override
public String filterType() {
return "pre";
} @Override
public int filterOrder() {
return 1;
}}