美文网首页springSpringCloud学习spring boot
Spring boot 2.0 整合 oauth2 SSO

Spring boot 2.0 整合 oauth2 SSO

作者: 风中吃西瓜 | 来源:发表于2018-07-09 17:00 被阅读1510次

    oauth2 sso 大致流程

    在一个公司中,肯定会存在多个不同的应用,比如公司的OA系统,HR系统等等,如果每个系统都用独立的账号认证体系,会给用户带来很大困扰,也给管理带来很大不便。所以通常需要设计一种统一登录的解决方案。比如我登陆了OA系统账号,进入HR系统时发现已经登录了,进入公司其他系统发现也自动登录了。使用SSO解决效果是一次输入密码多个应用都可以识别在线状态。

    1. 浏览器向客户端服务器请求接口触发要求安全认证
    2. 跳转到授权服务器获取授权许可码
    3. 从授权服务器带授权许可码跳回来
    4. 客户端服务器向授权服务器获取AccessToken
    5. 返回AccessToken到客户端服务器
    6. 发出/resource请求到客户端服务器
    7. 客户端服务器将/resource请求转发到Resource服务器
    8. Resource服务器要求安全验证,于是直接从授权服务器获取认证授权信息进行判断后(最后会响应给客户端服务器,客户端服务器再响应给浏览中器)

    SSO 角色

    1. 统一认证服务 AuthorizationServer
    2. SSO 客户端 OAuth2Sso

    工程结构

    image.png

    认证服务实现

    工程结构

    image.png

    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   # 应用名称
    
      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
    
      # 数据源 配置
      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 登录身份证认证

    @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"));
        }
    
    
    }
    

    权限认证服务配置 AuthorizationServerConfiguration

    /***
     *  身份授权认证服务配置
     *  配置客户端、token存储方式等
     */
    @Configuration
    @EnableAuthorizationServer  //  注解开启验证服务器 提供/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";   // 密码模式授权模式
        private static final String AUTHORIZATION_CODE = "authorization_code"; //授权码模式  授权码模式使用到了回调地址,是最为复杂的方式,通常网站中经常出现的微博,qq第三方登录,都会采用这个形式。
        private static final String REFRESH_TOKEN = "refresh_token";  //
        private static final String IMPLICIT = "implicit"; //简化授权模式
        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";    //指定哪些资源是需要授权验证的
    
    
        @Autowired
        private AuthenticationManager authenticationManager;   //认证方式
        @Resource(name = "userService")
        private UserDetailsService userDetailsService;
    
    
        @Override
        public void configure(ClientDetailsServiceConfigurer configurer) throws Exception {
            String secret = new BCryptPasswordEncoder().encode(CLIENT_SECRET);  // 用 BCrypt 对密码编码
            //配置3个个客户端,一个用于password认证、一个用于client认证、一个用于authorization_code认证
            configurer.inMemory()  // 使用in-memory存储
                    .withClient(CLIEN_ID_THREE)    //client_id用来标识客户的Id  客户端3
                    .resourceIds(RESOURCE_ID)
                    .authorizedGrantTypes(AUTHORIZATION_CODE,GRANT_TYPE, REFRESH_TOKEN,GRANT_TYPE_PASSWORD,IMPLICIT)  //允许授权类型
                    .scopes(SCOPE_READ,SCOPE_WRITE,TRUST)  //允许授权范围
                    .authorities("ROLE_CLIENT")  //客户端可以使用的权限
                    .secret(secret)  //secret客户端安全码
                    //.redirectUris(REDIRECT_URL)  //指定可以接受令牌和授权码的重定向URIs
                    .autoApprove(true) // 为true 则不会被重定向到授权的页面,也不需要手动给请求授权,直接自动授权成功返回code
                    .accessTokenValiditySeconds(ACCESS_TOKEN_VALIDITY_SECONDS)   //token 时间秒
                    .refreshTokenValiditySeconds(FREFRESH_TOKEN_VALIDITY_SECONDS);//刷新token 时间 秒
    
        }
    
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            endpoints.tokenStore(tokenStore()).authenticationManager(authenticationManager)
                    .accessTokenConverter(accessTokenConverter())
                    .userDetailsService(userDetailsService) //必须注入userDetailsService否则根据refresh_token无法加载用户信息
                    .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST,HttpMethod.OPTIONS)  //支持GET  POST  请求获取token
                    .reuseRefreshTokens(true) //开启刷新token
                    .tokenServices(tokenServices());
    
        }
    
    
        /**
         * 认证服务器的安全配置
         *
         * @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(); //允许表单认证  这段代码在授权码模式下会导致无法根据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();
                    //只有如下两种模式才能获取到当前用户信息
                    if("authorization_code".equals(grantType) || "password".equals(grantType)) {
                        String userName = authentication.getUserAuthentication().getName();
                        // 自定义一些token 信息 会在获取token返回结果中展示出来
                        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实现令牌(Access Token)
            return new JwtTokenStore(accessTokenConverter());
        }
    
        /**
         * 重写默认的资源服务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;
        }
    
    }
    

    资源服务认证配置 ResourceServerConfiguration

    
    @Configuration
    @EnableResourceServer   //注解来开启资源服务器
    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 {
           
            // 配置那些资源需要保护的
            http.requestMatchers().antMatchers("/api/**")
                    .and()
                    .authorizeRequests()
                    .antMatchers("/api/**").authenticated()
                    .and()
                    .exceptionHandling().accessDeniedHandler(customAccessDeniedHandler())  //权限认证失败业务处理
                    .authenticationEntryPoint(customAuthenticationEntryPoint());  //认证失败的业务处理
            http.addFilterBefore(permitAuthenticationFilter,AbstractPreAuthenticatedProcessingFilter.class); //自定义token过滤 token校验失败后自定义返回数据格式
        
        }
        @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 验证失败后自定义返回数据格式
         * @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()){
                        //自定义返回数据格式
                        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 {
            //解决静态资源被拦截的问题
            web.ignoring().antMatchers("/assets/**");
            web.ignoring().antMatchers("/favicon.ico");
    
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.csrf().disable()
                    .requestMatchers()   // requestMatchers 配置 数组
                    .antMatchers("/oauth/**","/login","/home")
                    .and()
                    .authorizeRequests()         //authorizeRequests 配置权限 顺序为先配置需要放行的url 在配置需要权限的url,最后再配置.anyRequest().authenticated()
                    .antMatchers("/oauth/**").authenticated()
                    .and()
                    .formLogin()
                    .loginPage("/login")
                    .permitAll();
            http.addFilterBefore(simpleCORSFilter, SecurityContextPersistenceFilter.class);
        }
    
    
    
        @Bean
        public BCryptPasswordEncoder bCryptPasswordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
    }
    
    

    跨域设置 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);
        }
    }
    

    自定义过滤器验证token 返回自定义数据格式 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("当前访问的URL地址:" +request.getRequestURI());
          Authentication authentication = this.tokenExtractor.extract(request);
          if (authentication == null) {
              if (this.stateless && this.isAuthenticated()) {
                 // SecurityContextHolder.clearContext();
              }
              log.info("当前访问的URL地址:" +request.getRequestURI() +"不进行拦截...");
              filterChain.doFilter(request, response);
          } else {
              log.info("************ 开始验证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("** 无校的token信息. ** ");
                      // throw new AccessDeniedException("无校的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("** 无校的token信息. ** ");
                      // throw new AccessDeniedException("无校的token信息.");
                  }
    
              }else {
                  error.set(true);
                  map.put("message","参数无token.");
                  log.info("** 参数无token. ** ");
                  //throw new AccessDeniedException("参数无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);
      }
    }
    

    页面跳转url注册 MvcConfig

    
    @Configuration
    public class MvcConfig implements  WebMvcConfigurer {
    
        @Override
        public void addViewControllers(ViewControllerRegistry registry) {
            registry.addViewController("/home").setViewName("home");
            registry.addViewController("/login").setViewName("login"); //自定义的登陆页面
            registry.addViewController("/oauth/confirm_access").setViewName("oauth_approval"); //自定义的授权页面
            registry.addViewController("/oauth_error").setViewName("oauth_error");
        }
    
    
    }
    

    自定义身份证认证失败返回数据格式 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("请求url:" +httpServletRequest.getRequestURI());
            log.info("  ============ 身份认证失败..................... ");
            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);
        }
    
    }
    

    测试 Controller UserController

    @CrossOrigin
    @RestController
    public class UserController {
    
        @GetMapping("oauth/me")
        public Principal getUser(Principal user){
            System.out.println(".. 进入 获取用户信息 方法   ..........  ");
            System.out.println(JSON.toJSONString(user));
            return user;
        }
    
        @GetMapping("api/user")
        public Principal user(Principal user){
            System.out.println(".. 进入 获取用户信息 方法   ..........  ");
            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("俏如来");
            list.add("帝如来");
            list.add("鬼如来");
            return list;
        }
    
        @RequestMapping(path = "api/messages", method = RequestMethod.POST)
       public String postMessage(Principal principal) {
            return "POST -> 默苍离 ";
        }
    
        /**
         * 当前登录人信息
         * @return
         */
        @RequestMapping(path = "api/loginUser", method = RequestMethod.GET)
        public UserDetails currentlyLoginUser(){
             UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
             return  userDetails;
        }
    
    
    }
    

    把字符串数据输出到页面 ResultUtil

    public class ResultUtil {
    
        /**
         * 将json输出到前端(参数非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");
            /* 设置浏览器跨域访问 */
            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();
            }
        }
    }
    

    启动类 SecurityOauth2AuthorizationServerApplication

    
    @SpringBootApplication
    public class SecurityOauth2AuthorizationServerApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(SecurityOauth2AuthorizationServerApplication.class, args);
        }
    }
    

    登录html页面 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 效果图:


    image.png

    首页 html 页面 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">
                                                               点击进入<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">
                                                                点击进入<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">
                                                                点击进入<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 效果图


    image.png

    SSO client 客户端

    项目结构

    image.png

    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   # 应用名称
    
      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
    
      # 数据源 配置
      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 认证配置
    oauth2-server: http://localhost:18082
    
    security:
      oauth2:
        client:
         # grant-type: client_credentials    # 授权模式
          client-id: client_3        # 在oauth 服务端注册的client-id
          client-secret: secret     # 在oauth 服务端注册的secret
          access-token-uri: ${oauth2-server}/oauth/token    #获取token 地址
          user-authorization-uri: ${oauth2-server}/oauth/authorize  # 认证地址
          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注解来开启SSO
    public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private SimpleCORSFilter simpleCORSFilter;
    
        @Value("${oauth2-server}")
        private String oauthServerUrl;
    
        @Override
        public void configure(WebSecurity web) throws Exception {
            //解决静态资源被拦截的问题
            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   //注解来开启资源服务器
    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);
    
            // 配置那些资源需要保护的
            http.csrf().disable()
                    .requestMatchers().antMatchers("/api/**")
                    .and()
                    .authorizeRequests()
                    .antMatchers("/api/**").authenticated()
                    .and()
                    .exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
            http.addFilterBefore(oAuth2AuthenticationFilter,AbstractPreAuthenticatedProcessingFilter.class); // 这种方式也可以达到token校验失败后自定义返回数据格式  使用此方式需要将上面的代码启用
        }
    
        /**
         * 重写 token 验证失败后自定义返回数据格式
         * @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()){
                        //自定义返回数据格式
                        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() {}
    
    }
    

    页面跳转url 注册 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();
        }
    }
    

    测试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","测试权限信息成功");
            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(".. 进入 获取用户信息 方法   ..........  ");
            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);
        }
    }
    

    启动类 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 页面 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 href="https://cdn.bootcss.com/layer/3.1.0/theme/default/layer.css" 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">
                        请求访问其他客户端资源 
                        <i class="icon-arrow-right"></i>
                    </span>
                    <span class="ladda-spinner"></span>
                </button>
            </div>
        </div>
    
        <div class="row">
            <div class="col-sm-12">
                <h1>获取其他服务登录人接口信息</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服务的当前登录人信息 
                        <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服务的登录人信息</h1>
                <div id="user_info"></div>
            </div>
        </div>
    
    
        <div class="row">
            <div class="col-sm-12">
                <h1>获取自身服务登录人接口信息</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服务的当前登录人信息 
                        <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服务的登录人信息</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服务的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 效果图


    image.png

    1. 请求授权访问18083端口应用服务

    http://localhost:18082/oauth/authorize?response_type=code&client_id=client_3&redirect_uri=http://localhost:18083/index
    如果处于未登录状态则会跳转到认证服务器的登录页面

    image.png

    2. 登录成功后回跳到http://localhost:18083/index 页面 并且携带code值

    image.png

    3. 根据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("请求获取token出现错误.");
                }
            });
    

    4. 携带token 访问认证服务端的资源接口

     $.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);
                }
            });
    

    返回数据:


    image.png

    5. 访问授权18082端口应用

    window.open("http://localhost:18082/oauth/authorize?response_type=code&client_id=client_3&redirect_uri=http://localhost:18082/home");
    

    如果已经处于登录状态 则直接进入http://localhost:18082/home页面

    image.png

    6. 携带token 访问18083端口服务接口资源

           $.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("请求获取localhost:18083/api/user服务登录人信息接口出错.");
                    }
                });
            })
    

    返回数据


    image.png

    7. 当请求访问资源未携带token 认证服务会进入 CustomAuthenticationEntryPoint 类中

    image.png image.png

    8. 当前请求携带错误的token 会在 PermitAuthenticationFilter 验证处理

    image.png
    image.png

    8. 未登录状态下访问sso client 客户端资源接口或者认证服务端的资源接口时 都会跳转到登录页面去

    image.png
    image.png
    image.png

    demo地址:

    相关文章

      网友评论

      本文标题:Spring boot 2.0 整合 oauth2 SSO

      本文链接:https://www.haomeiwen.com/subject/apftpftx.html