oauth2 sso 大致流程
在一個(gè)公司中失暂,肯定會(huì)存在多個(gè)不同的應(yīng)用遥金,比如公司的OA系統(tǒng)临燃,HR系統(tǒng)等等睛驳,如果每個(gè)系統(tǒng)都用獨(dú)立的賬號(hào)認(rèn)證體系,會(huì)給用戶帶來(lái)很大困擾膜廊,也給管理帶來(lái)很大不便乏沸。所以通常需要設(shè)計(jì)一種統(tǒng)一登錄的解決方案。比如我登陸了OA系統(tǒng)賬號(hào)爪瓜,進(jìn)入HR系統(tǒng)時(shí)發(fā)現(xiàn)已經(jīng)登錄了蹬跃,進(jìn)入公司其他系統(tǒng)發(fā)現(xiàn)也自動(dòng)登錄了。使用SSO解決效果是一次輸入密碼多個(gè)應(yīng)用都可以識(shí)別在線狀態(tài)铆铆。
- 瀏覽器向客戶端服務(wù)器請(qǐng)求接口觸發(fā)要求安全認(rèn)證
- 跳轉(zhuǎn)到授權(quán)服務(wù)器獲取授權(quán)許可碼
- 從授權(quán)服務(wù)器帶授權(quán)許可碼跳回來(lái)
- 客戶端服務(wù)器向授權(quán)服務(wù)器獲取AccessToken
- 返回AccessToken到客戶端服務(wù)器
- 發(fā)出/resource請(qǐng)求到客戶端服務(wù)器
- 客戶端服務(wù)器將/resource請(qǐng)求轉(zhuǎn)發(fā)到Resource服務(wù)器
- Resource服務(wù)器要求安全驗(yàn)證,于是直接從授權(quán)服務(wù)器獲取認(rèn)證授權(quán)信息進(jìn)行判斷后(最后會(huì)響應(yīng)給客戶端服務(wù)器,客戶端服務(wù)器再響應(yīng)給瀏覽中器)
SSO 角色
- 統(tǒng)一認(rèn)證服務(wù) AuthorizationServer
- SSO 客戶端 OAuth2Sso
工程結(jié)構(gòu)
認(rèn)證服務(wù)實(shí)現(xiàn)
工程結(jié)構(gòu)
pom.xml
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<security-jwt.version>1.0.9.RELEASE</security-jwt.version>
<jjwt.version>0.9.0</jjwt.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>${security-jwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.7</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
application.yml
server:
port: 18082
spring:
application:
name: oauth2-server # 應(yīng)用名稱
jpa:
open-in-view: true
database: POSTGRESQL
show-sql: true
hibernate:
ddl-auto: update
dialect: org.hibernate.dialect.PostgreSQLDialect
properties:
hibernate:
temp:
use_jdbc_metadata_defaults: false
# 數(shù)據(jù)源 配置
datasource:
platform: postgres
url: jdbc:postgresql://127.0.0.1:5432/cloud_oauth2?useUnicode=true&characterEncoding=utf-8
username: postgres
password: postgres123
driver-class-name: org.postgresql.Driver
redis:
host: 127.0.0.1
database: 0
thymeleaf:
prefix: classpath:/static/pages/
# 不需要攔截的url地址
mySecurity:
exclude:
antMatchers: /oauth/**,/login,/home
logging:
level:
org.springframework.security: DEBUG
Security 登錄身份證認(rèn)證
@Slf4j
@Service(value = "userService")
public class UserServiceImpl implements UserDetailsService {
@Autowired
private SysAccountRepository repository;
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SysAccount user = repository.findByUserAccount(username);
if(user == null){
log.info("登錄用戶【"+username + "】不存在.");
throw new UsernameNotFoundException("登錄用戶【"+username + "】不存在.");
}
return new org.springframework.security.core.userdetails.User(user.getUserAccount(), user.getUserPwd(), getAuthority());
}
private List getAuthority() {
return Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN"));
}
}
權(quán)限認(rèn)證服務(wù)配置 AuthorizationServerConfiguration
/***
* 身份授權(quán)認(rèn)證服務(wù)配置
* 配置客戶端蝶缀、token存儲(chǔ)方式等
*/
@Configuration
@EnableAuthorizationServer // 注解開啟驗(yàn)證服務(wù)器 提供/oauth/authorize,/oauth/token,/oauth/check_token,/oauth/confirm_access,/oauth/error
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
private static final String REDIRECT_URL = "https://www.baidu.com/";
private static final String CLIEN_ID_THREE = "client_3"; //客戶端3
private static final String CLIENT_SECRET = "secret"; //secret客戶端安全碼
private static final String GRANT_TYPE_PASSWORD = "password"; // 密碼模式授權(quán)模式
private static final String AUTHORIZATION_CODE = "authorization_code"; //授權(quán)碼模式 授權(quán)碼模式使用到了回調(diào)地址,是最為復(fù)雜的方式算灸,通常網(wǎng)站中經(jīng)常出現(xiàn)的微博扼劈,qq第三方登錄,都會(huì)采用這個(gè)形式菲驴。
private static final String REFRESH_TOKEN = "refresh_token"; //
private static final String IMPLICIT = "implicit"; //簡(jiǎn)化授權(quán)模式
private static final String GRANT_TYPE = "client_credentials"; //客戶端模式
private static final String SCOPE_READ = "read";
private static final String SCOPE_WRITE = "write";
private static final String TRUST = "trust";
private static final int ACCESS_TOKEN_VALIDITY_SECONDS = 1*60*60; //
private static final int FREFRESH_TOKEN_VALIDITY_SECONDS = 6*60*60; //
private static final String RESOURCE_ID = "resource_id"; //指定哪些資源是需要授權(quán)驗(yàn)證的
@Autowired
private AuthenticationManager authenticationManager; //認(rèn)證方式
@Resource(name = "userService")
private UserDetailsService userDetailsService;
@Override
public void configure(ClientDetailsServiceConfigurer configurer) throws Exception {
String secret = new BCryptPasswordEncoder().encode(CLIENT_SECRET); // 用 BCrypt 對(duì)密碼編碼
//配置3個(gè)個(gè)客戶端,一個(gè)用于password認(rèn)證荐吵、一個(gè)用于client認(rèn)證、一個(gè)用于authorization_code認(rèn)證
configurer.inMemory() // 使用in-memory存儲(chǔ)
.withClient(CLIEN_ID_THREE) //client_id用來(lái)標(biāo)識(shí)客戶的Id 客戶端3
.resourceIds(RESOURCE_ID)
.authorizedGrantTypes(AUTHORIZATION_CODE,GRANT_TYPE, REFRESH_TOKEN,GRANT_TYPE_PASSWORD,IMPLICIT) //允許授權(quán)類型
.scopes(SCOPE_READ,SCOPE_WRITE,TRUST) //允許授權(quán)范圍
.authorities("ROLE_CLIENT") //客戶端可以使用的權(quán)限
.secret(secret) //secret客戶端安全碼
//.redirectUris(REDIRECT_URL) //指定可以接受令牌和授權(quán)碼的重定向URIs
.autoApprove(true) // 為true 則不會(huì)被重定向到授權(quán)的頁(yè)面赊瞬,也不需要手動(dòng)給請(qǐng)求授權(quán),直接自動(dòng)授權(quán)成功返回code
.accessTokenValiditySeconds(ACCESS_TOKEN_VALIDITY_SECONDS) //token 時(shí)間秒
.refreshTokenValiditySeconds(FREFRESH_TOKEN_VALIDITY_SECONDS);//刷新token 時(shí)間 秒
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore()).authenticationManager(authenticationManager)
.accessTokenConverter(accessTokenConverter())
.userDetailsService(userDetailsService) //必須注入userDetailsService否則根據(jù)refresh_token無(wú)法加載用戶信息
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST,HttpMethod.OPTIONS) //支持GET POST 請(qǐng)求獲取token
.reuseRefreshTokens(true) //開啟刷新token
.tokenServices(tokenServices());
}
/**
* 認(rèn)證服務(wù)器的安全配置
*
* @param security
* @throws Exception
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
.realm(RESOURCE_ID)
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()") //isAuthenticated():排除anonymous isFullyAuthenticated():排除anonymous以及remember-me
.allowFormAuthenticationForClients(); //允許表單認(rèn)證 這段代碼在授權(quán)碼模式下會(huì)導(dǎo)致無(wú)法根據(jù)code 獲取token
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter() {
/**
* 自定義一些token返回的信息
* @param accessToken
* @param authentication
* @return
*/
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
String grantType = authentication.getOAuth2Request().getGrantType();
//只有如下兩種模式才能獲取到當(dāng)前用戶信息
if("authorization_code".equals(grantType) || "password".equals(grantType)) {
String userName = authentication.getUserAuthentication().getName();
// 自定義一些token 信息 會(huì)在獲取token返回結(jié)果中展示出來(lái)
final Map<String, Object> additionalInformation = new HashMap<>();
additionalInformation.put("user_name", userName);
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInformation);
}
OAuth2AccessToken token = super.enhance(accessToken, authentication);
return token;
}
};
converter.setSigningKey("bcrypt");
return converter;
}
@Bean
public TokenStore tokenStore() {
//基于jwt實(shí)現(xiàn)令牌(Access Token)
return new JwtTokenStore(accessTokenConverter());
}
/**
* 重寫默認(rèn)的資源服務(wù)token
* @return
*/
@Bean
public DefaultTokenServices tokenServices() {
final DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenEnhancer(accessTokenConverter());
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
defaultTokenServices.setAccessTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(30)); // 30天
return defaultTokenServices;
}
}
資源服務(wù)認(rèn)證配置 ResourceServerConfiguration
@Configuration
@EnableResourceServer //注解來(lái)開啟資源服務(wù)器
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
private static final String RESOURCE_ID = "resource_id";
@Autowired
private DefaultTokenServices tokenServices;
@Autowired
private TokenStore tokenStore;
@Autowired
private PermitAuthenticationFilter permitAuthenticationFilter;
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(RESOURCE_ID).stateless(true).tokenServices(tokenServices);
}
@Override
public void configure(HttpSecurity http) throws Exception {
// 配置那些資源需要保護(hù)的
http.requestMatchers().antMatchers("/api/**")
.and()
.authorizeRequests()
.antMatchers("/api/**").authenticated()
.and()
.exceptionHandling().accessDeniedHandler(customAccessDeniedHandler()) //權(quán)限認(rèn)證失敗業(yè)務(wù)處理
.authenticationEntryPoint(customAuthenticationEntryPoint()); //認(rèn)證失敗的業(yè)務(wù)處理
http.addFilterBefore(permitAuthenticationFilter,AbstractPreAuthenticatedProcessingFilter.class); //自定義token過濾 token校驗(yàn)失敗后自定義返回?cái)?shù)據(jù)格式
}
@Bean
public LogoutSuccessHandler customLogoutSuccessHandler(){
return new CustomLogoutSuccessHandler();
}
@Bean
public AuthenticationFailureHandler customLoginFailHandler(){
return new CustomLoginFailHandler();
}
@Bean
public OAuth2AuthenticationEntryPoint customAuthenticationEntryPoint(){
return new CustomAuthenticationEntryPoint();
}
@Bean
public OAuth2AccessDeniedHandler customAccessDeniedHandler(){
return new CustomAccessDenieHandler();
}
/**
* 重寫 token 驗(yàn)證失敗后自定義返回?cái)?shù)據(jù)格式
* @return
*/
@Bean
public WebResponseExceptionTranslator webResponseExceptionTranslator() {
return new DefaultWebResponseExceptionTranslator() {
@Override
public ResponseEntity translate(Exception e) throws Exception {
ResponseEntity responseEntity = super.translate(e);
OAuth2Exception body = (OAuth2Exception) responseEntity.getBody();
HttpHeaders headers = new HttpHeaders();
headers.setAll(responseEntity.getHeaders().toSingleValueMap());
// do something with header or response
if(401==responseEntity.getStatusCode().value()){
//自定義返回?cái)?shù)據(jù)格式
Map<String,String> map = new HashMap<>();
map.put("status","401");
map.put("message",e.getMessage());
map.put("timestamp", String.valueOf(LocalDateTime.now()));
return new ResponseEntity(JSON.toJSONString(map), headers, responseEntity.getStatusCode());
}else{
return new ResponseEntity(body, headers, responseEntity.getStatusCode());
}
}
};
}
}
web安全配置 SecurityConfiguration
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableAutoConfiguration(exclude = {
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class })
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private SimpleCORSFilter simpleCORSFilter;
@Resource(name = "userService")
private UserDetailsService userDetailsService;
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Autowired
public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(bCryptPasswordEncoder());
auth.parentAuthenticationManager(authenticationManagerBean());
}
@Override
public void configure(WebSecurity web) throws Exception {
//解決靜態(tài)資源被攔截的問題
web.ignoring().antMatchers("/assets/**");
web.ignoring().antMatchers("/favicon.ico");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.requestMatchers() // requestMatchers 配置 數(shù)組
.antMatchers("/oauth/**","/login","/home")
.and()
.authorizeRequests() //authorizeRequests 配置權(quán)限 順序?yàn)橄扰渲眯枰判械膗rl 在配置需要權(quán)限的url先煎,最后再配置.anyRequest().authenticated()
.antMatchers("/oauth/**").authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll();
http.addFilterBefore(simpleCORSFilter, SecurityContextPersistenceFilter.class);
}
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}
跨域設(shè)置 SimpleCORSFilter
@Slf4j
@Component
public class SimpleCORSFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
httpServletRequest.setCharacterEncoding("utf-8");
httpServletResponse.setCharacterEncoding("utf-8");
httpServletResponse.setHeader("Content-Type", "application/json");
httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");//允許所以域名訪問,
httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS");//允許的訪問方式
httpServletResponse.setHeader("Access-Control-Allow-Headers", "x-requested-with,content-type,Authorization");
httpServletResponse.setHeader("Access-Control-Request-Headers", "x-requested-with,content-type,Accept,Authorization");
httpServletResponse.setHeader("Access-Control-Request-Method", "GET,POST,PUT,DELETE,OPTIONS");
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
自定義過濾器驗(yàn)證token 返回自定義數(shù)據(jù)格式 PermitAuthenticationFilter
@Slf4j
@Component
public class PermitAuthenticationFilter extends OAuth2AuthenticationProcessingFilter {
private static final String BEARER_AUTHENTICATION = "Bearer ";
private static final String HEADER_AUTHORIZATION = "authorization";
private TokenExtractor tokenExtractor = new BearerTokenExtractor();
private boolean stateless = true;
OAuth2AuthenticationManager oAuth2AuthenticationManager = new OAuth2AuthenticationManager();
@Autowired
private TokenStore tokenStore;
public PermitAuthenticationFilter() {
DefaultTokenServices dt = new DefaultTokenServices();
dt.setTokenStore(tokenStore);
oAuth2AuthenticationManager.setTokenServices(dt);
this.setAuthenticationManager(oAuth2AuthenticationManager);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) servletResponse;
HttpServletRequest request = (HttpServletRequest)servletRequest;
log.info(" ================== =========================== ===================");
log.info("當(dāng)前訪問的URL地址:" +request.getRequestURI());
Authentication authentication = this.tokenExtractor.extract(request);
if (authentication == null) {
if (this.stateless && this.isAuthenticated()) {
// SecurityContextHolder.clearContext();
}
log.info("當(dāng)前訪問的URL地址:" +request.getRequestURI() +"不進(jìn)行攔截...");
filterChain.doFilter(request, response);
} else {
log.info("************ 開始驗(yàn)證token .......................... ");
String accessToken = request.getParameter("access_token");
String headerToken = request.getHeader(HEADER_AUTHORIZATION);
Map<String,String> map = new HashMap<>();
map.put("status","403");
AtomicBoolean error = new AtomicBoolean(false);
if(StringUtils.isNotBlank(accessToken)){
try {
OAuth2AccessToken oAuth2AccessToken = tokenStore.readAccessToken(accessToken);
log.info("token =" +oAuth2AccessToken.getValue());
}catch (InvalidTokenException e){
error.set(true);
map.put("message",e.getMessage());
log.info("** 無(wú)校的token信息. ** ");
// throw new AccessDeniedException("無(wú)校的token信息.");
}
}else if (StringUtils.isNotBlank(headerToken) && headerToken.startsWith(BEARER_AUTHENTICATION)){
try {
OAuth2AccessToken oAuth2AccessToken = tokenStore.readAccessToken(headerToken.split(" ")[0]);
log.info("token =" +oAuth2AccessToken.getValue());
}catch (InvalidTokenException e){
error.set(true);
map.put("message",e.getMessage());
log.info("** 無(wú)校的token信息. ** ");
// throw new AccessDeniedException("無(wú)校的token信息.");
}
}else {
error.set(true);
map.put("message","參數(shù)無(wú)token.");
log.info("** 參數(shù)無(wú)token. ** ");
//throw new AccessDeniedException("參數(shù)無(wú)token.");
}
if (!error.get()){
filterChain.doFilter(request, response);
}else {
map.put("path", request.getServletPath());
map.put("timestamp", String.valueOf(LocalDateTime.now()));
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
ResultUtil.writeJavaScript(response,map);
}
}
}
@Override
public void destroy() {
}
private boolean isAuthenticated() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return authentication != null && !(authentication instanceof AnonymousAuthenticationToken);
}
}
頁(yè)面跳轉(zhuǎn)url注冊(cè) MvcConfig
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/home").setViewName("home");
registry.addViewController("/login").setViewName("login"); //自定義的登陸頁(yè)面
registry.addViewController("/oauth/confirm_access").setViewName("oauth_approval"); //自定義的授權(quán)頁(yè)面
registry.addViewController("/oauth_error").setViewName("oauth_error");
}
}
自定義身份證認(rèn)證失敗返回?cái)?shù)據(jù)格式 CustomAuthenticationEntryPoint
@Slf4j
@Component
public class CustomAuthenticationEntryPoint extends OAuth2AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
log.info(" ====================================================== ");
log.info("請(qǐng)求url:" +httpServletRequest.getRequestURI());
log.info(" ============ 身份認(rèn)證失敗..................... ");
log.info(e.getMessage());
log.info(e.getLocalizedMessage());
e.printStackTrace();
Map<String,String> map = new HashMap<>();
map.put("status","401");
map.put("message",e.getMessage());
map.put("path", httpServletRequest.getServletPath());
map.put("timestamp", String.valueOf(LocalDateTime.now()));
httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
ResultUtil.writeJavaScript(httpServletResponse,map);
}
}
測(cè)試 Controller UserController
@CrossOrigin
@RestController
public class UserController {
@GetMapping("oauth/me")
public Principal getUser(Principal user){
System.out.println(".. 進(jìn)入 獲取用戶信息 方法 .......... ");
System.out.println(JSON.toJSONString(user));
return user;
}
@GetMapping("api/user")
public Principal user(Principal user){
System.out.println(".. 進(jìn)入 獲取用戶信息 方法 .......... ");
System.out.println(JSON.toJSONString(user));
return user;
}
@RequestMapping(path = "api/messages", method = RequestMethod.GET)
public List<String> getMessages(Principal principal) {
List<String> list = new LinkedList<>();
list.add("俏如來(lái)");
list.add("帝如來(lái)");
list.add("鬼如來(lái)");
return list;
}
@RequestMapping(path = "api/messages", method = RequestMethod.POST)
public String postMessage(Principal principal) {
return "POST -> 默蒼離 ";
}
/**
* 當(dāng)前登錄人信息
* @return
*/
@RequestMapping(path = "api/loginUser", method = RequestMethod.GET)
public UserDetails currentlyLoginUser(){
UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
return userDetails;
}
}
把字符串?dāng)?shù)據(jù)輸出到頁(yè)面 ResultUtil
public class ResultUtil {
/**
* 將json輸出到前端(參數(shù)非json格式)
* @param response
* @param obj 任意類型
*/
public static void writeJavaScript(HttpServletResponse response, Object obj){
response.setContentType("application/json;charset=UTF-8");
response.setHeader("Cache-Control","no-store, max-age=0, no-cache, must-revalidate");
response.addHeader("Cache-Control", "post-check=0, pre-check=0");
response.setHeader("Pragma", "no-cache");
/* 設(shè)置瀏覽器跨域訪問 */
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST,GET,OPTIONS,DELETE,PUT");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with,Authorization");
response.setHeader("Access-Control-Allow-Credentials","true");
try {
PrintWriter out = response.getWriter();
out.write(JSON.toJSONString(obj));
out.flush();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
啟動(dòng)類 SecurityOauth2AuthorizationServerApplication
@SpringBootApplication
public class SecurityOauth2AuthorizationServerApplication {
public static void main(String[] args) {
SpringApplication.run(SecurityOauth2AuthorizationServerApplication.class, args);
}
}
登錄html頁(yè)面 login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>OAuth2 SSO login</title>
<link href="../assets/global/plugins/font-awesome/css/font-awesome.min.css" rel="stylesheet" type="text/css" />
<link href="../assets/global/plugins/simple-line-icons/simple-line-icons.min.css" rel="stylesheet" type="text/css" />
<link href="../assets/global/plugins/bootstrap/css/bootstrap.min.css" rel="stylesheet" type="text/css" />
<link href="../assets/global/plugins/bootstrap-switch/css/bootstrap-switch.min.css" rel="stylesheet" type="text/css" />
<link href="../assets/global/css/components.min.css" rel="stylesheet" id="style_components" type="text/css" />
<link href="../assets/global/css/plugins.min.css" rel="stylesheet" type="text/css" />
<link href="../assets/pages/css/login.min.css" rel="stylesheet" type="text/css" />
</head>
<body class=" login">
<div class="content" style="margin-top: 10%">
<!-- BEGIN LOGIN FORM -->
<form class="login-form" action="login" role="form" method="post">
<h3 class="form-title">Login to your account</h3>
<div class="alert alert-danger display-hide">
<button class="close" data-close="alert"></button>
<span> Enter any username and password. </span>
</div>
<div class="form-group">
<!--ie8, ie9 does not support html5 placeholder, so we just show field title for that-->
<label class="control-label visible-ie8 visible-ie9">Username</label>
<div class="input-icon">
<i class="fa fa-user"></i>
<input class="form-control placeholder-no-fix" type="text" autocomplete="off" placeholder="Username" name="username" value="mocangli"/> </div>
</div>
<div class="form-group">
<label class="control-label visible-ie8 visible-ie9">Password</label>
<div class="input-icon">
<i class="fa fa-lock"></i>
<input class="form-control placeholder-no-fix" type="password" autocomplete="off" placeholder="Password" name="password" value="123456"/> </div>
</div>
<div class="form-actions">
<label class="rememberme mt-checkbox mt-checkbox-outline">
<input type="checkbox" name="remember" value="1" /> Remember me
<span></span>
</label>
<button type="submit" id="login-button" class="btn green pull-right"> Login </button>
<!-- <button type="button" id="login-button" class="btn green pull-right"> Login </button>-->
</div>
</form>
<!-- END LOGIN FORM -->
</div>
<script src="../assets/global/plugins/jquery.min.js" type="text/javascript"></script>
<script src="../assets/global/plugins/bootstrap/js/bootstrap.min.js" type="text/javascript"></script>
<script src="../assets/global/plugins/js.cookie.min.js" type="text/javascript"></script>
<script src="../assets/global/plugins/jquery-slimscroll/jquery.slimscroll.min.js" type="text/javascript"></script>
<script src="../assets/global/plugins/jquery.blockui.min.js" type="text/javascript"></script>
<script src="../assets/global/plugins/bootstrap-switch/js/bootstrap-switch.min.js" type="text/javascript"></script>
<script src="../assets/global/plugins/backstretch/jquery.backstretch.min.js" type="text/javascript"></script>
<script src="../assets/global/plugins/jquery-validation/js/jquery.validate.min.js" type="text/javascript"></script>
<script src="../assets/global/scripts/app.min.js" type="text/javascript"></script>
</body>
</html>
login.html 效果圖:
首頁(yè) html 頁(yè)面 home.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>OAuth2 SSO login</title>
<link href="../assets/global/plugins/font-awesome/css/font-awesome.min.css" rel="stylesheet" type="text/css" />
<link href="../assets/global/plugins/simple-line-icons/simple-line-icons.min.css" rel="stylesheet" type="text/css" />
<link href="../assets/global/plugins/bootstrap/css/bootstrap.min.css" rel="stylesheet" type="text/css" />
<link href="../assets/global/plugins/bootstrap-switch/css/bootstrap-switch.min.css" rel="stylesheet" type="text/css" />
<link href="../assets/global/css/components.min.css" rel="stylesheet" id="style_components" type="text/css" />
<link href="../assets/global/css/plugins.min.css" rel="stylesheet" type="text/css" />
<link href="../assets/global/plugins/bootstrap-toastr/toastr.min.css" rel="stylesheet" type="text/css" />
<link href="../assets/layouts/layout/css/layout.min.css" rel="stylesheet" type="text/css">
<link href="../assets/layouts/layout/css/themes/darkblue.min.css" rel="stylesheet" type="text/css" id="style_color">
<link href="../assets/layouts/layout/css/custom.css" rel="stylesheet" type="text/css" />
</head>
<body class=" page-header-fixed page-sidebar-closed-hide-logo page-content-white">
<div class="page-content-wrapper">
<div class="page-content">
<div class="row">
<div class="col-lg-3 col-md-3 col-sm-6 col-xs-12">
<a class="dashboard-stat dashboard-stat-v2 blue" href="#">
<div class="visual">
<i class="fa fa-comments"></i>
</div>
<div class="details">
<div class="number">
<span data-counter="counterup" data-value="client_1">client_1</span>
</div>
<div class="desc" style="padding-top: 10px">
<button type="button" id="client_1_btn" class="btn green mt-ladda-btn ladda-button btn-circle btn-outline" data-style="slide-down" data-spinner-color="#333">
<span class="ladda-label">
點(diǎn)擊進(jìn)入<i class="fa fa-arrow-circle-right"></i> </span>
<span class="ladda-spinner"></span></button>
</div>
</div>
</a>
</div>
<div class="col-lg-3 col-md-3 col-sm-6 col-xs-12">
<a class="dashboard-stat dashboard-stat-v2 green" href="#">
<div class="visual">
<i class="fa fa-shopping-cart"></i>
</div>
<div class="details">
<div class="number">
<span data-counter="counterup" data-value="client_2">client_2</span>
</div>
<div class="desc" style="padding-top: 10px">
<button type="button" id="client_2_btn" class="btn blue mt-ladda-btn ladda-button btn-circle btn-outline" data-style="slide-down" data-spinner-color="#333">
<span class="ladda-label">
點(diǎn)擊進(jìn)入<i class="fa fa-arrow-circle-right"></i> </span>
<span class="ladda-spinner"></span></button>
</div>
</div>
</a>
</div>
<div class="col-lg-3 col-md-3 col-sm-6 col-xs-12">
<a class="dashboard-stat dashboard-stat-v2 purple" href="#">
<div class="visual">
<i class="fa fa-globe"></i>
</div>
<div class="details">
<div class="number">
<span data-counter="counterup" data-value="client_3">client_3</span>
</div>
<div class="desc" style="padding-top: 10px">
<button type="button" id="client_3_btn" class="btn green mt-ladda-btn ladda-button btn-circle btn-outline" data-style="slide-down" data-spinner-color="#333">
<span class="ladda-label">
點(diǎn)擊進(jìn)入<i class="fa fa-arrow-circle-right"></i> </span>
<span class="ladda-spinner"></span></button>
</div>
</div>
</a>
</div>
</div>
</div>
</div>
<script src="../assets/global/plugins/jquery.min.js" type="text/javascript"></script>
<script src="../assets/global/plugins/bootstrap/js/bootstrap.min.js" type="text/javascript"></script>
<script src="../assets/global/plugins/js.cookie.min.js" type="text/javascript"></script>
<script src="../assets/global/plugins/jquery-slimscroll/jquery.slimscroll.min.js" type="text/javascript"></script>
<script src="../assets/global/plugins/jquery.blockui.min.js" type="text/javascript"></script>
<script src="../assets/global/plugins/bootstrap-switch/js/bootstrap-switch.min.js" type="text/javascript"></script>
<script src="../assets/global/plugins/backstretch/jquery.backstretch.min.js" type="text/javascript"></script>
<script src="../assets/global/plugins/bootstrap-toastr/toastr.min.js" type="text/javascript"></script>
<script src="../assets/global/scripts/app.min.js" type="text/javascript"></script>
<script src="../assets/layouts/layout/scripts/layout.min.js" type="text/javascript"></script>
<script src="../assets/pages/scripts/home.js" type="text/javascript"></script>
</body>
</html>
home.html 效果圖
SSO client 客戶端
項(xiàng)目結(jié)構(gòu)
pom.xml
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<security-jwt.version>1.0.9.RELEASE</security-jwt.version>
<jjwt.version>0.9.0</jjwt.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>${security-jwt.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
application.yml
server:
port: 18083
spring:
application:
name: oauth2-sso-client1 # 應(yīng)用名稱
jpa:
open-in-view: true
database: POSTGRESQL
show-sql: true
hibernate:
ddl-auto: update
dialect: org.hibernate.dialect.PostgreSQLDialect
properties:
hibernate:
temp:
use_jdbc_metadata_defaults: false
# 數(shù)據(jù)源 配置
datasource:
platform: postgres
url: jdbc:postgresql://127.0.0.1:5432/cloud_oauth2?useUnicode=true&characterEncoding=utf-8
username: postgres
password: postgres123
driver-class-name: org.postgresql.Driver
redis:
host: 127.0.0.1
database: 0
thymeleaf:
prefix: classpath:/static/pages/
#cache: false
security:
user:
name: user
password: e94a652b-adfb-4af7-ba00-d88419289172
# sso 認(rèn)證配置
oauth2-server: http://localhost:18082
security:
oauth2:
client:
# grant-type: client_credentials # 授權(quán)模式
client-id: client_3 # 在oauth 服務(wù)端注冊(cè)的client-id
client-secret: secret # 在oauth 服務(wù)端注冊(cè)的secret
access-token-uri: ${oauth2-server}/oauth/token #獲取token 地址
user-authorization-uri: ${oauth2-server}/oauth/authorize # 認(rèn)證地址
scope: read,write
resource:
token-info-uri: ${oauth2-server}/oauth/check_token # 檢查token
user-info-uri: ${oauth2-server}/oauth/me # 用戶信息
jwt:
key-uri: ${oauth2-server}/oauth/token_key
sso:
login-path: /login
logging:
level:
org.springframework.security: DEBUG
web 安全配置 SecurityConfiguration
@Configuration
@EnableWebSecurity
@EnableOAuth2Sso //@EnableOAuth2Sso注解來(lái)開啟SSO
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private SimpleCORSFilter simpleCORSFilter;
@Value("${oauth2-server}")
private String oauthServerUrl;
@Override
public void configure(WebSecurity web) throws Exception {
//解決靜態(tài)資源被攔截的問題
web.ignoring().antMatchers("/assets/**");
web.ignoring().antMatchers("/favicon.ico");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.requestMatchers()
.antMatchers("/oauth/**","/login","/index")
.and()
.authorizeRequests()
.antMatchers("/oauth/**").authenticated()
.and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler())
.and()
.formLogin()
.permitAll()
.loginProcessingUrl("/index");
http.addFilterBefore(simpleCORSFilter,SecurityContextPersistenceFilter.class);
}
}
資源配置 ResourceConfiguration
@Configuration
@EnableResourceServer //注解來(lái)開啟資源服務(wù)器
public class ResourceConfiguration extends ResourceServerConfigurerAdapter {
private static final String RESOURCE_ID = "resource_id";
@Autowired
private DefaultTokenServices tokenServices;
@Autowired
private TokenStore tokenStore;
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(RESOURCE_ID).stateless(true).tokenServices(tokenServices);
}
@Override
public void configure(HttpSecurity http) throws Exception {
//如果 啟用 http.addFilterBefore(oAuth2AuthenticationFilter,AbstractPreAuthenticatedProcessingFilter.class) 代碼 則需要啟用下面被注釋的代碼
OAuth2AuthenticationProcessingFilter oAuth2AuthenticationFilter = new OAuth2AuthenticationProcessingFilter();
OAuth2AuthenticationEntryPoint oAuth2AuthenticationEntryPoint = new OAuth2AuthenticationEntryPoint();
oAuth2AuthenticationEntryPoint.setExceptionTranslator(webResponseExceptionTranslator());
oAuth2AuthenticationFilter.setAuthenticationEntryPoint(oAuth2AuthenticationEntryPoint);
OAuth2AuthenticationManager oAuth2AuthenticationManager = new OAuth2AuthenticationManager();
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore);
oAuth2AuthenticationManager.setTokenServices(defaultTokenServices);
oAuth2AuthenticationFilter.setAuthenticationManager(oAuth2AuthenticationManager);
// 配置那些資源需要保護(hù)的
http.csrf().disable()
.requestMatchers().antMatchers("/api/**")
.and()
.authorizeRequests()
.antMatchers("/api/**").authenticated()
.and()
.exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
http.addFilterBefore(oAuth2AuthenticationFilter,AbstractPreAuthenticatedProcessingFilter.class); // 這種方式也可以達(dá)到token校驗(yàn)失敗后自定義返回?cái)?shù)據(jù)格式 使用此方式需要將上面的代碼啟用
}
/**
* 重寫 token 驗(yàn)證失敗后自定義返回?cái)?shù)據(jù)格式
* @return
*/
@Bean
public WebResponseExceptionTranslator webResponseExceptionTranslator() {
return new DefaultWebResponseExceptionTranslator() {
@Override
public ResponseEntity translate(Exception e) throws Exception {
ResponseEntity responseEntity = super.translate(e);
OAuth2Exception body = (OAuth2Exception) responseEntity.getBody();
HttpHeaders headers = new HttpHeaders();
headers.setAll(responseEntity.getHeaders().toSingleValueMap());
// do something with header or response
if(401==responseEntity.getStatusCode().value()){
//自定義返回?cái)?shù)據(jù)格式
Map<String,String> map = new HashMap<>();
map.put("status","401");
map.put("message",e.getMessage());
map.put("timestamp", String.valueOf(LocalDateTime.now()));
return new ResponseEntity(JSON.toJSONString(map), headers, responseEntity.getStatusCode());
}else{
return new ResponseEntity(body, headers, responseEntity.getStatusCode());
}
}
};
}
}
跨域 SimpleCORSFilter
@Component
public class SimpleCORSFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
String token = req.getParameter("access_token");
System.out.println(" token -- "+ token);
if(!StringUtils.isEmpty(token)){
TokenContextHolder.setToken(token);
}
HttpServletResponse response = (HttpServletResponse) res;
response.setCharacterEncoding("utf-8");
response.setCharacterEncoding("utf-8");
response.setHeader("Content-Type", "application/json");
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with,content-type,Accept,Authorization");
chain.doFilter(req, res);
}
@Override
public void init(FilterConfig filterConfig) {}
@Override
public void destroy() {}
}
頁(yè)面跳轉(zhuǎn)url 注冊(cè) MvcConfiguration
@Configuration
public class MvcConfiguration implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/index").setViewName("index");
registry.addViewController("/securedPage");
}
}
token 存放 TokenContextHolder
public class TokenContextHolder {
private static final ThreadLocal<String> LOCAL_TOKEN = new ThreadLocal<>();
public static void setToken(String value){
LOCAL_TOKEN.set(value);
}
public static String getToken(){
String token = LOCAL_TOKEN.get();
clearToken();
return token;
}
public static void clearToken(){
LOCAL_TOKEN.remove();
}
}
測(cè)試Controller HomeController
@CrossOrigin
@RestController
public class HomeController {
@Value("${oauth2-server}")
private String serverUrl;
@Autowired
IMessageService messageService;
@Autowired
OAuth2RestTemplate oAuth2RestTemplate;
@RequestMapping("/getMessages")
public List<String> getMessages(){
List<String> list = oAuth2RestTemplate.getForObject(serverUrl+"/api/messages",List.class);
list.stream().forEach(item ->{
System.out.println(item);
});
return list;
}
@RequestMapping("api/test")
public String test(){
Map<String,String> map = new HashMap<>();
map.put("code","0");
map.put("msg","測(cè)試權(quán)限信息成功");
System.out.println(JSON.toJSONString(map));
return JSON.toJSONString(map);
}
@RequestMapping("/postMessages")
public String postMessage(){
String token = TokenContextHolder.getToken();
String str = oAuth2RestTemplate.postForObject(serverUrl+"api/messages?access_token="+token,null,String.class);
Map<String,String> map = new HashMap<>();
map.put("msg",str);
System.out.println(JSON.toJSONString(map));
return JSON.toJSONString(map);
}
@GetMapping("api/user")
public String user(){
System.out.println(".. 進(jìn)入 獲取用戶信息 方法 .......... ");
String token = TokenContextHolder.getToken();
String str = oAuth2RestTemplate.getForObject(serverUrl+"api/user?access_token="+token,String.class);
System.out.println(JSON.toJSONString(str));
return JSON.toJSONString(str);
}
}
啟動(dòng)類 Oauth2SsoClient1Application
@SpringBootApplication
public class Oauth2SsoClient1Application {
@Bean
OAuth2RestTemplate oauth2RestTemplate(OAuth2ClientContext oauth2ClientContext, OAuth2ProtectedResourceDetails details) {
return new OAuth2RestTemplate(details, oauth2ClientContext);
}
public static void main(String[] args) {
SpringApplication.run(Oauth2SsoClient1Application.class, args);
}
}
html 頁(yè)面 index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>OAuth2 SSO Demo</title>
<link href="../assets/global/plugins/font-awesome/css/font-awesome.min.css" rel="stylesheet" type="text/css" />
<link href="../assets/global/plugins/simple-line-icons/simple-line-icons.min.css" rel="stylesheet" type="text/css" />
<link href="../assets/global/plugins/bootstrap/css/bootstrap.min.css" rel="stylesheet" type="text/css" />
<link href="../assets/global/plugins/bootstrap-switch/css/bootstrap-switch.min.css" rel="stylesheet" type="text/css" />
<link href="../assets/global/plugins/bootstrap-toastr/toastr.min.css" rel="stylesheet" type="text/css" />
<link rel="stylesheet">
</head>
<body>
<div class="container">
<div class="row">
<div class="col-sm-12">
<h1>Spring Security SSO</h1>
<a class="btn btn-primary" href="securedPage" id="sso_btn">Login</a>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<h1>authorization_code</h1>
<button type="button" id="request_auth_code_btn" class="btn btn-primary mt-ladda-btn ladda-button btn-circle" data-style="expand-right" onclick="Index.authorization()">
<span class="ladda-label">
請(qǐng)求訪問其他客戶端資源
<i class="icon-arrow-right"></i>
</span>
<span class="ladda-spinner"></span>
</button>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<h1>獲取其他服務(wù)登錄人接口信息</h1>
<button type="button" id="user_btn" class="btn btn-primary mt-ladda-btn ladda-button btn-circle" data-style="expand-right" onclick="Index.userInfo()">
<span class="ladda-label">
獲取localhost:18082服務(wù)的當(dāng)前登錄人信息
<i class="icon-arrow-right"></i>
</span>
<span class="ladda-spinner"></span>
</button>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<h1>localhost:18082服務(wù)的登錄人信息</h1>
<div id="user_info"></div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<h1>獲取自身服務(wù)登錄人接口信息</h1>
<button type="button" id="localhost_user_btn" class="btn btn-primary mt-ladda-btn ladda-button btn-circle" data-style="expand-right" onclick="Index.localUserInfo()">
<span class="ladda-label">
獲取localhost:18083服務(wù)的當(dāng)前登錄人信息
<i class="icon-arrow-right"></i>
</span>
<span class="ladda-spinner"></span>
</button>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<h1>localhost:18083服務(wù)的登錄人信息</h1>
<div id="localhost_user_info"></div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<h1>獲取自身postMessages接口信息</h1>
<button type="button" id="localhost_msg_user_btn" class="btn btn-primary mt-ladda-btn ladda-button btn-circle" data-style="expand-right" onclick="Index.localMsgInfo()">
<span class="ladda-label">
獲取localhost:18083服務(wù)的postMessages接口信息
<i class="icon-arrow-right"></i>
</span>
<span class="ladda-spinner"></span>
</button>
</div>
</div>
<div id="loginModal" class="modal fade" role="dialog" tabindex="-1">
</div>
</div>
<script src="../assets/global/plugins/jquery.min.js" type="text/javascript"></script>
<script src="../assets/global/plugins/bootstrap/js/bootstrap.min.js" type="text/javascript"></script>
<script src="../assets/global/plugins/jquery.blockui.min.js" type="text/javascript"></script>
<script src="../assets/global/plugins/bootstrap-switch/js/bootstrap-switch.min.js" type="text/javascript"></script>
<script src="../assets/global/plugins/bootstrap-toastr/toastr.min.js" type="text/javascript"></script>
<script src="../assets/global/scripts/app.min.js" type="text/javascript"></script>
<script src="https://cdn.bootcss.com/layer/3.1.0/layer.js"></script>
<script src="../assets/pages/scripts/index.js" type="text/javascript"></script>
</body>
</html>
index.html 效果圖
1. 請(qǐng)求授權(quán)訪問18083端口應(yīng)用服務(wù)
http://localhost:18082/oauth/authorize?response_type=code&client_id=client_3&redirect_uri=http://localhost:18083/index
如果處于未登錄狀態(tài)則會(huì)跳轉(zhuǎn)到認(rèn)證服務(wù)器的登錄頁(yè)面
2. 登錄成功后回跳到http://localhost:18083/index 頁(yè)面 并且攜帶code值
3. 根據(jù)code 值 獲取token
$.ajax({
url:"http://localhost:18082/oauth/token?grant_type=authorization_code&client_id=client_3&client_secret=secret&redirect_uri=http://localhost:18083/index&code="+code,
type:'get',
dataType:'json',
withCredentials: true,
success:function(data,textStatus,XMLHttpRequest){
console.log(data);
access_token = data.access_token;
},
error:function(xhr,status,error){
toastr.error("請(qǐng)求獲取token出現(xiàn)錯(cuò)誤.");
}
});
4. 攜帶token 訪問認(rèn)證服務(wù)端的資源接口
$.ajax({
url:"http://localhost:18082/api/user",
data:{
"access_token":access_token
},
type:'get',
dataType:'json',
withCredentials: true,
success:function(data,textStatus,XMLHttpRequest){
console.log(data);
App.alert({
container: "#user_info",
message:JSON.stringify(data),
close: true,
icon: 'fa fa-user',
closeInSeconds: 1000
});
},
error:function(xhr,status,error){
var obj = JSON.parse(xhr.responseText);
toastr.error(obj.message);
}
});
返回?cái)?shù)據(jù):
5. 訪問授權(quán)18082端口應(yīng)用
window.open("http://localhost:18082/oauth/authorize?response_type=code&client_id=client_3&redirect_uri=http://localhost:18082/home");
如果已經(jīng)處于登錄狀態(tài) 則直接進(jìn)入http://localhost:18082/home頁(yè)面
6. 攜帶token 訪問18083端口服務(wù)接口資源
$.ajax({
url:"http://localhost:18083/api/user",
data:{
"access_token":access_token
},
type:'get',
dataType:'json',
withCredentials: true,
success:function(data,textStatus,XMLHttpRequest){
console.log(data);
App.alert({
container: "#user_info",
message:JSON.stringify(data),
close: true,
icon: 'fa fa-user',
closeInSeconds: 1000
});
toastr.success("登錄人信息",JSON.stringify(data));
},
error:function(xhr,status,error){
console.log(xhr);
toastr.error("請(qǐng)求獲取localhost:18083/api/user服務(wù)登錄人信息接口出錯(cuò).");
}
});
})
返回?cái)?shù)據(jù)
7. 當(dāng)請(qǐng)求訪問資源未攜帶token 認(rèn)證服務(wù)會(huì)進(jìn)入 CustomAuthenticationEntryPoint 類中
8. 當(dāng)前請(qǐng)求攜帶錯(cuò)誤的token 會(huì)在 PermitAuthenticationFilter 驗(yàn)證處理
8. 未登錄狀態(tài)下訪問sso client 客戶端資源接口或者認(rèn)證服務(wù)端的資源接口時(shí) 都會(huì)跳轉(zhuǎn)到登錄頁(yè)面去
demo地址: