Spring Cloud Gateway 整合 Spring Security Oauth2


Spring Cloud Gateway 整合 Spring Security Oauth2

最近一直在搞Spring Cloud Gateway 整合 Spring Security Oauth2,網上找了很多資料,搞了好幾天,今天終於搞完了,先來說一下具體思路吧。(里面只展示一些主要的代碼)

服務名 注釋 描述
zswyAuth-8850 認證服務器 實現一個簡單的基本的 oauth2認證服務 使用 jwt token,使用自定義 JwtTokenStore
zswyGateway-9527 網關 實現簡單的鑒權服務,調用zswyAuth-8850的auth/check_token進行token校檢、鑒權,最后通過gateway進行調用
zswyBlog 資源服務器 使用 spring cloud gateway 實現簡單路由,實現統一路由轉發

主要思路: 首先在認證服務器上面獲取token,在網關上進行驗證,然后通過網關到資源服務器上面獲取資源。因為是自己的項目,所以里面主要是用到密碼模式實現的。

認證服務器

image

pom.xml
點擊查看代碼
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>zswyhou</artifactId>
        <groupId>cn.edu.zswyhou</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.edu.zswyAuth-8850</groupId>
    <artifactId>zswyAuth-8850</artifactId>

    <dependencies>
        <!--springboot-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--SpringCloud ailibaba nacos -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
<!--        lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!--        認證鑒權-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
        <!--            mysql連接池-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--swagger的依賴-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
        </dependency>

        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
        </dependency>

        <!--MyBatis-Plus的依賴-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-core</artifactId>
            <scope>compile</scope>
        </dependency>
        <!--hutool工具類-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
        </dependency>

        <!--        加入自己定義的工具模塊-->
        <dependency>
            <groupId>cn.edu.zswyhou.zswyCommon</groupId>
            <artifactId>zswyCommon</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

</project>

里面主要是依賴主要是認證鑒權依賴實現認證服務器的,其他依賴是我這個項目所需的依賴

認證服務器主要核心類
點擊查看代碼
package cn.edu.zswyauth.security.config;

import cn.edu.zswyauth.security.exception.WebResponseTranslator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;

import javax.sql.DataSource;

@Configuration
@EnableAuthorizationServer
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private AuthorizationCodeServices authorizationCodeServices;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private AuthorizationServerTokenServices tokenService;

    @Autowired
    @Qualifier("myClientDetailsService")
    private ClientDetailsService clientService;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    /**
     * 配置客戶端詳細信息服務
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {

        clients.inMemory()
                //配置client-id
                .withClient("admin")
                //配置client-secret
                .secret(passwordEncoder().encode("112233"))
                //token的有效期
                .accessTokenValiditySeconds(30000)
                //刷新令牌的有效期,即refreshToken
                .refreshTokenValiditySeconds(40000)
                //授權成功后跳轉的地址,授權碼模式用到
                .redirectUris("https://www.baidu.com/")
                //自動授權配置
                .autoApprove(true)
                //配置申請的權限范圍
                .scopes("all")
                //表示授權類型,密碼模式
                .authorizedGrantTypes("refresh_token","authorization_code","password");
    }

    @Bean("myClientDetailsService")
    public ClientDetailsService clientDetailsService(DataSource dataSource, PasswordEncoder passwordEncoder) {
        JdbcClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);
        clientDetailsService.setPasswordEncoder(passwordEncoder);
        return clientDetailsService;
    }


    /**
     * 令牌訪問端點
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints
                .authenticationManager(authenticationManager)
                .authorizationCodeServices(authorizationCodeServices)
                .tokenServices(tokenService)
                .allowedTokenEndpointRequestMethods(HttpMethod.POST)
                .exceptionTranslator(new WebResponseTranslator());

    }

    /**
     * 令牌訪問端點安全策略
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) {
        security
                .tokenKeyAccess("permitAll()")//oauth/token_key 公開密鑰
                .checkTokenAccess("permitAll()")//oauth/check_token公開
                .allowFormAuthenticationForClients();// 允許表單認證
    }
}
tokenConfig配置類
點擊查看代碼
package cn.edu.zswyauth.security.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.*;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.List;

@Configuration
public class TokenConfig {
    /**
     * 秘鑰串
     */
    private static final String SIGNING_KEY = "uaa";


    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey(SIGNING_KEY);
        return converter;
    }

    /**
     * 配置令牌管理
     */
    @Bean
    public AuthorizationServerTokenServices tokenService(ClientDetailsService clientDetailsService, TokenStore tokenStore
            , JwtAccessTokenConverter accessTokenConverter) {
        DefaultTokenServices service = new DefaultTokenServices();
        service.setClientDetailsService(clientDetailsService);
        service.setSupportRefreshToken(true);
        service.setTokenStore(tokenStore);
        //加入增強器tokenEnhancer
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> list=new ArrayList<TokenEnhancer>();
        list.add(myTokenEnhancer());
        list.add(accessTokenConverter);
        tokenEnhancerChain.setTokenEnhancers(list);
        service.setTokenEnhancer(tokenEnhancerChain);
        return service;
    }

    /**
     * 授權碼存儲方式,其實這個也可以不寫,因為用的是密碼模式,沒有授權碼
     */

    @Bean
    public AuthorizationCodeServices authorizationCodeServices(DataSource dataSource) {
        return new JdbcAuthorizationCodeServices(dataSource);
    }
    @Bean
    public MyTokenEnhancer myTokenEnhancer(){
        return new MyTokenEnhancer();
    }
}
MytokenEnhancer增強器(增加token里面的信息,信息可以自己定義)
點擊查看代碼
package cn.edu.zswyauth.security.config;

import cn.edu.zswyauth.entity.User;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;

import java.util.HashMap;
import java.util.Map;

/**
 *token的增前器,增加payload的字段
 */
public class MyTokenEnhancer implements TokenEnhancer {
    public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
        User user=new User();
        Map<String,Object> map=new HashMap<String,Object>();
        map.put("user_id",user.getUseId());
        ((DefaultOAuth2AccessToken)oAuth2AccessToken).setAdditionalInformation(map);
        return oAuth2AccessToken;
    }
}
WebSecurityConfig主要配置類(負責定義攔截哪些路徑或允許哪些路徑通過)
點擊查看代碼
package cn.edu.zswyauth.security.config;

import cn.edu.zswyauth.security.handle.FailureHandler;
import cn.edu.zswyauth.security.handle.LogoutHandler;
import cn.edu.zswyauth.security.handle.SuccessHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;


@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {


    @Autowired
    private SuccessHandler successHandler;

    @Autowired
    private FailureHandler failureHandler;

    @Autowired
    private LogoutHandler logoutHandler;


    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable().formLogin()
//                .loginProcessingUrl("/login").permitAll()
                .successHandler(successHandler).permitAll()
                .failureHandler(failureHandler).permitAll().and()
                .logout().logoutSuccessHandler(logoutHandler).and()
                .authorizeRequests()
                .antMatchers("/**").permitAll();
    }
}
MyUserDetailService(查詢用戶是否存在)
點擊查看代碼
package cn.edu.zswyauth.security.service;


import cn.edu.zswyauth.entity.User;
import cn.edu.zswyauth.service.UserService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

@Component
public class MyUserDetailService implements UserDetailsService {
    private PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Autowired
    @Qualifier("MyUserService")
    private UserService userService;
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        QueryWrapper<User> wrapper=new QueryWrapper<User>();
        wrapper.eq("user_number",username);
        User user = userService.getOne(wrapper);
        //判斷用戶是否存在
        if (user == null) {
            return null;
        }
        System.out.println(user);
        boolean accountNonExpired = user.getUserIsaccountnonexpired() == 1 ? true : false;
        boolean accountnonlocked = user.getUserIsaccountnonlocked() == 1 ? true : false;
        boolean credentialsnonexpired = user.getUserIscredentialsnonexpired() == 1 ? true : false;
        boolean enableds = user.getUserIsenabled() == 1 ? true : false;
        //獲取權限
        String userRole = user.getUserRole();
        System.out.println(userRole);
        //返回一個user用戶,注意user是security里的用戶
        UserDetails u =new org.springframework.security.core.userdetails.User(user.getUserNumber(), passwordEncoder().encode(user.getUserPassword()), enableds,accountNonExpired,credentialsnonexpired,accountnonlocked, AuthorityUtils.commaSeparatedStringToAuthorityList(userRole));
        return u;
    }
}

FailureHandler
點擊查看代碼
package cn.edu.zswyauth.security.handle;

import cn.edu.zswyCommon.response.JSONAuthentication;
import cn.edu.zswyCommon.response.Result;
import cn.edu.zswyCommon.response.ResultCode;
import org.springframework.security.authentication.*;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component("myAuthenticationFailureHandler")
public class FailureHandler extends JSONAuthentication implements AuthenticationFailureHandler {
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        Result result = null;
        if (e instanceof AccountExpiredException) {
            //賬號過期
            result = Result.error(ResultCode.USER_ACCOUNT_EXPIRED);
        } else if (e instanceof BadCredentialsException) {
            //密碼錯誤
            result = Result.error(ResultCode.USER_CREDENTIALS_ERROR);
        } else if (e instanceof CredentialsExpiredException) {
            //密碼過期
            result = Result.error(ResultCode.USER_CREDENTIALS_EXPIRED);
        } else if (e instanceof DisabledException) {
            //賬號不可用
            result = Result.error(ResultCode.USER_ACCOUNT_DISABLE);
        } else if (e instanceof LockedException) {
            //賬號鎖定
            result = Result.error(ResultCode.USER_ACCOUNT_LOCKED);
        } else if (e instanceof InternalAuthenticationServiceException) {
            //用戶不存在
            result = Result.error(ResultCode.USER_ACCOUNT_NOT_EXIST);
        }else{
            //其他錯誤
            result = Result.error(ResultCode.COMMON_FAIL);
        }
        this.WriteJSON(httpServletRequest,httpServletResponse,result);
    }
}

LogoutHandler
點擊查看代碼
package cn.edu.zswyauth.security.handle;

import cn.edu.zswyCommon.response.JSONAuthentication;
import cn.edu.zswyCommon.response.Result;
import cn.edu.zswyCommon.response.ResultCode;
import cn.hutool.core.util.StrUtil;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component("myLogoutHandler")
public class LogoutHandler extends JSONAuthentication implements LogoutSuccessHandler {
    public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        Result result = Result.ok(ResultCode.SUCCESS).message("注銷成功");
        this.WriteJSON(httpServletRequest,httpServletResponse,result);
    }
}
SuccessHandler
點擊查看代碼
@Component("myAuthenticationSuccessHandler")
public class SuccessHandler extends JSONAuthentication implements AuthenticationSuccessHandler {
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        //只需要以json的格式返回一個提示就行了
        //Result類的Json數據
        Result result = Result.ok(ResultCode.SUCCESS).message("登錄成功");
        this.WriteJSON(httpServletRequest,httpServletResponse,result);
    }
}
WebResponseTranslator(獲取token時用戶名錯誤的處理)
點擊查看代碼
public class WebResponseTranslator implements WebResponseExceptionTranslator {
    public ResponseEntity translate(Exception e) throws Exception {
        if (e instanceof InternalAuthenticationServiceException) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(new OAuth2Exception("賬號密碼錯誤"));
        }
        return ResponseEntity
                .status(HttpStatus.OK)
                .body(new OAuth2Exception(e.getMessage()));
    }
}
zswyAuthMain8850
點擊查看代碼
@SpringBootApplication
@EnableDiscoveryClient
@MapperScan("cn/edu/zswyauth/mapper")
public class zswyAuthMain8850 {
    public static void main(String[] args) {
        SpringApplication.run(zswyAuthMain8850.class,args);
    }
}
application.yml
點擊查看代碼
server:
  port: 8850
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/xxx?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
    username: root
    password: 123456
  application:
    name: zswyAuth-8850
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848

#暴露監控
management:
  endpoints:
    web:
      exposure:
        include: '*'

網關

image

pom.xml
點擊查看代碼
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>zswyhou</artifactId>
        <groupId>cn.edu.zswyhou</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.edu.zswyGateway-9527</groupId>
    <artifactId>zswyGateway-9527</artifactId>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <!--gateway-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!--SpringCloud ailibaba nacos -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </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>
        <!--授權-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-security</artifactId>
        </dependency>
		<!--        網關這個依賴千萬不要加,不然網關的過濾器不起作用-->
<!--        <dependency>-->
<!--            <groupId>org.springframework.cloud</groupId>-->
<!--            <artifactId>spring-cloud-starter-oauth2</artifactId>-->
<!--        </dependency>-->
<!--        <dependency>-->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-jwt</artifactId>
            <version>1.0.9.RELEASE</version>
        </dependency>

        <!--hutool工具類-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
        </dependency>

        <!--        加入自己定義的工具模塊-->
        <dependency>
            <groupId>cn.edu.zswyhou.zswyCommon</groupId>
            <artifactId>zswyCommon</artifactId>
            <version>1.0-SNAPSHOT</version>
            <exclusions>
                <exclusion>
                    <groupId>io.springfox</groupId>
                    <artifactId>springfox-spring-web</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-collections4</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>

    </dependencies>
</project>

AuthClient(認證客戶端)
點擊查看代碼
@Component
public class AuthClient {
    private static final Logger LOGGER = LoggerFactory.getLogger(AuthClient.class);

    private RestTemplate restTemplate = new RestTemplate();

    private String checkTokenUrl="http://localhost:8850/oauth/check_token";


    public HashMap<Object,String> accessable(ServerHttpRequest request, String token) {
        //造和編碼URI,默認是utf-8
//        加入請求頭
        HttpEntity<?> entity = new HttpEntity<>(request.getHeaders());

        ParameterizedTypeReference<String> myBean = new  ParameterizedTypeReference<String>() {};

        try {
//            調用zswyAuth-8850的check_token,檢查token
            ResponseEntity<String> responseEntity = restTemplate.exchange(checkTokenUrl + "?token=" + token, HttpMethod.POST, entity, myBean);
//            日志
            LOGGER.info("oauth request: {}, response body: {}, reponse status: {}",
                    entity, responseEntity.getBody(), responseEntity.getStatusCode());
//            得到返回的數據
            String body = responseEntity.getBody();
//            把String轉為HashMap類型
            HashMap<Object,String> hashMap = JSON.parseObject(body,HashMap.class);
            System.out.println(hashMap);
            return hashMap;
        } catch (RestClientException e) {
            LOGGER.error("oauth failed.", e);
        }
        return null;
    }
}
GatewayCorsConfiguration(網關跨域的配置類)
點擊查看代碼
@Configuration
public class GatewayCorsConfiguration {

    @Bean
    public CorsWebFilter corsWebFilter(){
        System.out.println("進入corsWebFilter");
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");
        corsConfiguration.addAllowedOrigin("*");
        corsConfiguration.setAllowCredentials(true);
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**",corsConfiguration);
        return new CorsWebFilter(source);
    }

}

MyLogGateWayFilter(全局過濾器)
點擊查看代碼
@Configuration
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter, Ordered {

//    @Autowired
//    @Qualifier("tokenStore")
//    private TokenStore tokenStore;

    @Autowired
    private AuthClient client;
    @Autowired
    private TokenUntils tokenUntils;
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("time:"+new Date()+"\t 執行了自定義的全局過濾器: "+"MyLogGateWayFilter"+"hello");

        String requestUrl = exchange.getRequest().getPath().value();
        AntPathMatcher pathMatcher = new AntPathMatcher();
        //1 auth服務所有放行
        if (pathMatcher.match("/oauth/**", requestUrl)) {
            return chain.filter(exchange);
        }
        //2 檢查token是否存在
        String token = tokenUntils.getToken(exchange);
        if (StringUtils.isBlank(token)) {
            return tokenUntils.noTokenMono(exchange);
        }else {
            HashMap accessable = client.accessable(exchange.getRequest(), token);
            //判斷用戶憑證是否存在
            if (accessable.get("user_name")==null){
               return tokenUntils.invalidTokenMono(exchange);
            }
            //獲取憑證
            Object principal = accessable.get("user_name");
            //獲取用戶權限
            List<String> authorities = (List<String>) accessable.get("authorities");
            JSONObject jsonObject=new JSONObject();
            jsonObject.put("principal",principal);
            jsonObject.put("authorities",authorities);
            //轉為base64編碼
            String base64 = EncryptUtil.encodeUTF8StringBase64(jsonObject.toJSONString());
            //把base64加入請求頭中
            ServerHttpRequest tokenRequest = exchange.getRequest().mutate().header("json-token", base64).build();
            ServerWebExchange build = exchange.mutate().request(tokenRequest).build();
            return chain.filter(exchange);

        }
    }

/**
*這個數值越小,越先執行該過濾器
*/
    @Override
    public int getOrder() {
        return 0;
    }
}
WebSecurityConfig(Security的配置類)
點擊查看代碼
@Configuration
@EnableWebFluxSecurity
public class WebSecurityConfig{
    @Bean
    public ServerCodecConfigurer serverCodecConfigurer() {
        return ServerCodecConfigurer.create();
    }
    @Bean
    SecurityWebFilterChain webFluxSecurityFilterChain(ServerHttpSecurity http){
        System.out.println("進入webFluxSecurityFilterChain");
        return http.authorizeExchange()
                .pathMatchers("/**").permitAll()
                .anyExchange().authenticated()
                .and().csrf().disable().build();
    }
}
TokenUntils(token的工具類)
點擊查看代碼
@Component
public class TokenUntils {
    /**
     * 獲取token
     */
    public String getToken(ServerWebExchange exchange) {
        String tokenStr = exchange.getRequest().getHeaders().getFirst("Authorization");
        if (StringUtils.isBlank(tokenStr)) {
            return null;
        }
        String token = tokenStr.split(" ")[1];
        if (StringUtils.isBlank(token)) {
            return null;
        }
        return token;
    }


    /**
     * 無效的token
     */
    public Mono<Void> invalidTokenMono(ServerWebExchange exchange) {
        JSONObject json = new JSONObject();
        json.put("status", HttpStatus.UNAUTHORIZED.value());
        json.put("data", "無效的token");
        return buildReturnMono(json, exchange);
    }

    public Mono<Void> noTokenMono(ServerWebExchange exchange) {
        JSONObject json = new JSONObject();
        json.put("status", HttpStatus.UNAUTHORIZED.value());
        json.put("data", "沒有token");
        return buildReturnMono(json, exchange);
    }


    public Mono<Void> buildReturnMono(JSONObject json, ServerWebExchange exchange) {
        ServerHttpResponse response = exchange.getResponse();
        byte[] bits = json.toJSONString().getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(bits);
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        //指定編碼,否則在瀏覽器中會中文亂碼
        response.getHeaders().add("Content-Type", "text/plain;charset=UTF-8");
        return response.writeWith(Mono.just(buffer));
    }
}
zswyGateway9527(主程序類)
點擊查看代碼
@SpringBootApplication
@EnableDiscoveryClient
public class zswyGateway9527 {
    public static void main(String[] args) {
        SpringApplication.run(zswyGateway9527.class,args);
    }
}
application.yml
點擊查看代碼
server:
  port: 9527
spring:
  application:
    name: zswy-gateway-9527
  nacos:
    discovery:
      server-addr: 8848
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true #開啟從注冊中心動態創建路由的功能,利用微服務名進行路由
      routes:
        - id: zswyBlog_routh #payment_route    #路由的ID,沒有固定規則但要求唯一,建議配合服務名
          # uri: http://localhost:8001          #匹配后提供服務的路由地址
          uri: lb://zswyBlog-8001 #匹配后提供服務的路由地址,lb為負載均衡的其中一種模式
          predicates:
           # 斷言,路徑相匹配的進行路由
            - Path= /blog/**
        - id: zswyAuth-8850_routh #payment_route    #路由的ID,沒有固定規則但要求唯一,建議配合服務名
          uri: lb://zswyAuth-8850 #匹配后提供服務的路由地址
          predicates:
            # 斷言,路徑相匹配的進行路由
            - Path= /oauth/**
#  解決某些錯誤,啟動覆蓋
  main:
    allow-bean-definition-overriding: true

資源服務器

image

pom.xml
點擊查看代碼
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>zswyhou</artifactId>
        <groupId>cn.edu.zswyhou</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.edu.zswyBlog</groupId>
    <artifactId>zswyBlog</artifactId>

    <dependencies>
<!--        加入自己定義的工具模塊-->
        <dependency>
            <groupId>cn.edu.zswyhou.zswyCommon</groupId>
            <artifactId>zswyCommon</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!--SpringCloud ailibaba nacos -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <!--web-actuator-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
<!--            <scope>test</scope>-->
        </dependency>
        <!--            mysql連接池-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--swagger的依賴-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
        </dependency>

        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
        </dependency>

        <!--MyBatis-Plus的依賴-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>

        <!--代碼自動生成器-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
        </dependency>

        <!--添加 模板引擎 依賴,MyBatis-Plus 支持 Velocity(默認)-->
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
        </dependency>

        <!--hutool工具類-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
        </dependency>
<!--        <dependency>-->
<!--            <groupId>com.baomidou</groupId>-->
<!--            <artifactId>mybatis-plus-core</artifactId>-->
<!--&lt;!&ndash;            <scope>test</scope>&ndash;&gt;-->
<!--        </dependency>-->
<!--        <dependency>-->
<!--            <groupId>com.baomidou</groupId>-->
<!--            <artifactId>mybatis-plus-annotation</artifactId>-->
<!--&lt;!&ndash;            <scope>compile</scope>&ndash;&gt;-->
<!--        </dependency>-->

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-core</artifactId>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
<!--        阿里雲儲存OS-->
        <dependency>
            <groupId>com.aliyun.oss</groupId>
            <artifactId>aliyun-sdk-oss</artifactId>
        </dependency>
        <!--授權-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security.oauth.boot</groupId>
            <artifactId>spring-security-oauth2-autoconfigure</artifactId>
            <version>2.1.2.RELEASE</version>
            <scope>compile</scope>
        </dependency>
<!--JSONObject-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.47</version>
        </dependency>
    </dependencies>

</project>

資源服務核心配置類
點擊查看代碼
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResouceServerConfig extends ResourceServerConfigurerAdapter {
    @Autowired
    private TokenStore tokenStore;

    /**
     * 資源ID,與認證服務器上的client-id相同
     */
    private static final String RESOURCE_ID = "admin";


    /**
     *  資源配置
     */
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources.resourceId(RESOURCE_ID)
                .tokenStore(tokenStore)
                .stateless(true)
                .accessDeniedHandler(new CustomAccessDeniedHandler());
    }

    /**
     * 請求配置
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/**").permitAll()
                .and().csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }
}

TokenConfig
點擊查看代碼
@Configuration
public class TokenConfig {
    /**
     * 密匙串,一定要與zswyAuth-8850的密匙串一樣,否則校檢不通過
     */
    private static final String SIGNING_KEY = "uaa";


    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey(SIGNING_KEY);
        return converter;
    }
}
CustomAccessDeniedHandler(沒有權限的操作)
點擊查看代碼
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        JSONObject json=new JSONObject();
        json.put("code",403);
        json.put("msg","沒有權限");
        httpServletResponse.setHeader("Content-type", "text/html;charset=UTF-8");
        httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
        httpServletResponse.getWriter().write(JSONObject.toJSONString(json));
    }
}
AuthenticationFilter(認證過濾器,獲取到傳過來的用戶名和權限)
點擊查看代碼
@Component
public class AuthenticationFilter extends OncePerRequestFilter {
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        System.out.println("進入AuthenticationFilter");
        String token = request.getHeader("json-token");
        if (StringUtils.isNotBlank(token)){
            //base64解碼
            String json = EncryptUtil.decodeUTF8StringBase64(token);
            //String轉為JSONObject
            JSONObject jsonObject = JSON.parseObject(json);
            //獲取用戶身份信息
            String principal = jsonObject.getString("principal");
            //獲取權限信息
            JSONArray tempJsonArray = jsonObject.getJSONArray("authorities");
            //存入數組中
            String[] authorities =  tempJsonArray.toArray(new String[0]);
            //身份信息、權限信息填充到用戶身份token對象中
            UsernamePasswordAuthenticationToken authenticationToken=new UsernamePasswordAuthenticationToken(principal,null,
                    AuthorityUtils.createAuthorityList(authorities));
            //創建details
            authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            //將authenticationToken填充到安全上下文
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        }
        filterChain.doFilter(request,response);
    }
}

zswyBlogMain
點擊查看代碼
@EnableDiscoveryClient
@SpringBootApplication
@MapperScan("cn/edu/zswyblog/mapper")
public class zswyBlogMain {
    public static void main(String[] args) {
        SpringApplication.run(zswyBlogMain.class,args);
    }
}
UserController(控制器)
點擊查看代碼
@RestController
@RequestMapping("/blog/use")
public class UserController extends JSONAuthentication {
    @Autowired
    private BlogMapper blogMapper;
    @PostMapping("/use")
	//定義權限
    @PreAuthorize("hasAnyAuthority('user','ROLE_admin')")
    public void get(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        System.out.println("進入get");
        Blog blog = blogMapper.selectById(1);
        Map<String,Object> map=new HashMap<String, Object>();
        map.put("blog",blog);
        Result result=Result.ok(ResultCode.SUCCESS).data(map);
        this.WriteJSON(request,response,result);
    }

}

有nacos的先啟動nacos,沒有的不用啟動,然后依次啟動zswyAuth-8850,zswyBlog,zswyGateway-9527

username和password分別是與認證服務器的client-id和配置client-secret里的值

grant_type 是指定以什么模式進行,password是密碼模式
scope是指定范圍,all為所有范圍
username是用戶名
password是密碼

獲取令牌,此時access_token就是token,你可以用它來訪問一些資源,refresh_token是刷新令牌

然后我們復制這個token到請求頭去請求資源

以下就是資源請求成功后返回的數據

好了,這樣Spring Cloud Gateway 整合 Spring Security Oauth2就好了,如果有錯誤的地方歡迎指正。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM