security + oauth2 + jwt + gateway 實現(xiàn)統(tǒng)一身份認證和鑒權(quán)(基礎(chǔ))

spring security + oauth2 + jwt + gateway 實現(xiàn)統(tǒng)一身份認證

├──security-user        --認證服務(wù)
├──gateway-forward      --統(tǒng)一網(wǎng)關(guān)
├──order-server         --資源服務(wù)
├──resources            --數(shù)據(jù)庫等靜態(tài)資源

基礎(chǔ)講解

四種授權(quán)模式

"authorization_code", "password", "client_credentials", "implicit", "refresh_token"

  • 密碼模式(resource owner password credentials)(為遺留系統(tǒng)設(shè)計)(支持refresh token)
  • 授權(quán)碼模式(authorization code)(正宗方式)(支持refresh token)
  • 簡化模式(implicit)(為web瀏覽器應(yīng)用設(shè)計)(不支持refresh token)
  • 客戶端模式(client credentials)(為后臺api服務(wù)消費者設(shè)計)(不支持refresh token)
  1. 授權(quán)碼模式 authorization_code
  • 這種模式算是正宗的oauth2的授權(quán)模式, 最安全
  • 設(shè)計了auth code,通過這個code再獲取token
  • 支持refresh token

授權(quán)碼的請求路徑

/oauth/authorize?client_id=c1&response_type=code&scope=all&redirect_url=http://www.baidu.com

獲取token

/oauth/token?client_id=c1&response_type=code&scope=all&redirect_url=http://www.baidu.com
  1. 簡化模式 implicit
  • 這種模式比授權(quán)碼模式少了code環(huán)節(jié)壶运,回調(diào)url直接攜帶token
  • 這種模式的使用場景是基于瀏覽器的應(yīng)用
  • 這種模式基于安全性考慮嗦哆,建議把token時效設(shè)置短一些
  • 不支持refresh token

經(jīng)過授權(quán)后直接返回access_token,

/oauth/authorize?client_id=c1&response_type=token&scope=all&redirect_url=http://www.baidu.com

返回值
https://www.baidu.com/#access_token=20ea0a59-a043-492f-86cf-836f948f58cc&token_type=bearer&expires_in=6637
https://www.baidu.com/#access_token=access_toke&token_type=bearer&expires_in=7199&jti=9c7765db-f140-4545-a4ae-3f8cc7b6ba6e
  1. 密碼模式 password
  • 這種模式是最不推薦的硬萍,會將密碼泄露給client
  • 這種模式主要用來做遺留項目升級為oauth2的適配方案
  • 當然如果client是自家的應(yīng)用,也是可以
  • 支持refresh token
post 請求
/oauth/token
參數(shù)
client_id=c1&client_secret=secret&grant_type=password&username=admin&password=1111
  1. 客戶端模式 client_credentials
  • 這種模式直接根據(jù)client的id和密鑰即可獲取token疗锐,無需用戶參與
  • 這種模式比較合適消費api的后端服務(wù),比如拉取一組用戶信息等
  • 不支持refresh token,主要是沒有必要

refresh token的初衷主要是為了用戶體驗不想用戶重復(fù)輸入賬號密碼來換取新token畴椰,因而設(shè)計了refresh token用于換取新token

這種模式由于沒有用戶參與,而且也不需要用戶賬號密碼鸽粉,僅僅根據(jù)自己的id和密鑰就可以換取新token斜脂,因而沒必要refresh token

post 請求
/oauth/token
參數(shù)
client_id=c1&client_secret=secret&grant_type=client_credentials

資源服務(wù)

  • 校驗token
/oauth/check_token?token=289d1f2d-26f5-4f2d-9542-6c4d3f0a936c

訪問資源
http://localhost:8205/order/r/r1?abc=123

網(wǎng)關(guān)整合auth2

網(wǎng)關(guān)整合auth2有兩種方式

  1. 認證服務(wù)器生成jwt令牌,所有請求統(tǒng)一在網(wǎng)關(guān)層驗證触机,判斷權(quán)限
  2. 由資源服務(wù)處理帚戳,網(wǎng)關(guān)只做請求轉(zhuǎn)發(fā)

使用spring gateway作為網(wǎng)關(guān)

  1. 建立認證服務(wù)器
    1.1. 父工程依賴
<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
    </parent>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>javax.servlet-api</artifactId>
                <version>3.1.0</version>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>javax.interceptor</groupId>
                <artifactId>javax.interceptor-api</artifactId>
                <version>1.2</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>1.2.47</version>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.0</version>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.21</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
                <version>1.1.22</version>
            </dependency>

            <dependency>
                <groupId>org.springframework.security</groupId>
                <artifactId>spring-security-jwt</artifactId>
                <version>1.0.1.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.security.oauth.boot</groupId>
                <artifactId>spring-security-oauth2-autoconfigure</artifactId>
                <version>2.1.3.RELEASE</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <build>
        <finalName>${project.name}</finalName>
        <resources>
            <resource>
                <directory>src/main/resource</directory>
                <filtering>true</filtering>
                <includes>
                    <include>**/*</include>
                </includes>
            </resource>
        </resources>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-resources-plugin</artifactId>
                <configuration>
                    <encoding>utf-8</encoding>
                    <useDefaultDelimiters>true</useDefaultDelimiters>
                </configuration>
            </plugin>
        </plugins>
    </build>

1.2. 認證服務(wù)器依賴

<dependencies>
<!--        <dependency>-->
<!--            <groupId>org.springframework.cloud</groupId>-->
<!--            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>-->
<!--        </dependency>-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>com.netflix.hystrix</groupId>
            <artifactId>hystrix-javanica</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.retry</groupId>
            <artifactId>spring-retry</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-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-commons</artifactId>
        </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>
            <!--<exclusions>-->
            <!--<exclusion>-->
            <!--<groupId>org.springframework.security.oauth.boot</groupId>-->
            <!--<artifactId>spring-security-oauth2-autoconfigure</artifactId>-->
            <!--</exclusion>-->
            <!--</exclusions>-->
        </dependency>

        <dependency>
            <groupId>javax.interceptor</groupId>
            <artifactId>javax.interceptor-api</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-jwt</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

1.3. 配置文件 application.yml

server:
  port: 8202

spring:
  application:
    name: user-server
  datasource:
    url: jdbc:mysql://localhost:3306/user-center?serverTimezone=PRC&useUnicode=true&characterEncoding=utf8&useSSL=true
    username: root
    password: 
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
  main:
    allow-bean-definition-overriding: true

1.4. 認證服務(wù)器配置(核心類AuthorizationServerConfigurerAdapter)
認證服務(wù)器配置類必須繼承AuthorizationServerConfigurerAdapter類

import org.springframework.beans.factory.annotation.Autowired;
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.code.InMemoryAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

import javax.sql.DataSource;
import java.util.Arrays;

@Configuration
@EnableAuthorizationServer
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private AuthorizationCodeServices authorizationCodeServices;
//
//    @Bean
//    public AuthorizationCodeServices authorizationCodeServices()
//    {
//        return new InMemoryAuthorizationCodeServices();
//    }

    @Bean
    public AuthorizationCodeServices authorizationCodeServices(DataSource dataSource)
    {
        return new JdbcAuthorizationCodeServices(dataSource);
    }

    @Autowired
    private JwtAccessTokenConverter jwtAccessTokenConverter;


    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private ClientDetailsService clientDetailsService;

    @Autowired
    private PasswordEncoder passwordEncoder;

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


    // 配置客戶端詳細信息
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//        clients.inMemory() // 使用內(nèi)存存儲
//
//            .withClient("c1") // client_id
//            // 客戶端密鑰
//            .secret(new BCryptPasswordEncoder().encode("secret"))
//            // 資源列表
//            .resourceIds("res1")
//            // 授權(quán)方式
//            .authorizedGrantTypes("authorization_code","password","client_credentials","implicit","refresh_token")
//            // 允許授權(quán)的范圍
//            .scopes("all")
//            //
//            .autoApprove(false)  // false 跳轉(zhuǎn)到授權(quán)頁面
//            // 加上驗證回調(diào)地址
//            .redirectUris("http://www.baidu.com");
        clients.withClientDetails(clientDetailsService);

    }



    // 令牌管理服務(wù)
    public AuthorizationServerTokenServices tokenServices()
    {
        DefaultTokenServices service = new DefaultTokenServices();
        service.setClientDetailsService(clientDetailsService);  // 客戶端信息服務(wù)
        service.setSupportRefreshToken(true);   // 是否產(chǎn)生刷新令牌
        service.setTokenStore(tokenStore);  // 設(shè)置令牌存儲策略
        // 令牌增強
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(jwtAccessTokenConverter));
        service.setTokenEnhancer(tokenEnhancerChain);
        service.setAccessTokenValiditySeconds(7200);// 令牌默認有效期 2 小時
        service.setRefreshTokenValiditySeconds(259200);
        return service;
    }

    /**
     * /oauth/authorize     授權(quán)端點
     * /oauth/token         令牌斷點
     * /oauth/confirm-access用戶確認授權(quán)提交端點
     * /auth/error          授權(quán)服務(wù)錯誤信息斷電
     * /auth/check_token    用戶資源服務(wù)訪問的令牌解析斷電
     * /oauth/token_key     提供公有密鑰的端點,如果你使用jwt令牌的話
     */

    /**
     *  令怕i訪問端點
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                // 密碼管理模式
                .authenticationManager(authenticationManager)
                // 授權(quán)碼模式
                .authorizationCodeServices(authorizationCodeServices)
                .tokenServices(tokenServices()) // 令牌管理服務(wù)
                .allowedTokenEndpointRequestMethods(HttpMethod.POST); // 允許post提交
    }


    /**
     *  令牌訪問端點的安全策略
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.tokenKeyAccess("permitAll()")         // /oauth/token_key  公開
                .checkTokenAccess("permitAll()")       // /auth/check_token  檢測令牌
                .allowFormAuthenticationForClients();  // 允許通過表單認證儡首,申請令牌

    }
}

1.4.1. 內(nèi)存存儲token方案

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

@Configuration
public class TokenConfig {
    //內(nèi)存存儲token方案
//    @Bean
//    public TokenStore tokenStore() {
//        return new InMemoryTokenStore();
//    }
    //jwt令牌存儲方案
    private static final String SIGNING_KEY = "uaa123";
    // 令牌存儲策略
    @Bean
    public TokenStore tokenStore()
    {
        // Jwt令牌存儲方案
        return new JwtTokenStore(assessTokenConverter());
    }
    @Bean
    public JwtAccessTokenConverter assessTokenConverter()
    {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey(SIGNING_KEY);//
        return converter;
    }
}

1.4.2. 配置Spring Security的安全認證

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;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    //認證管理器
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    // 安全攔截機制
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
//                .antMatchers("/r/r1").hasAnyAuthority("p1")
                .antMatchers("/login*").permitAll()
                .anyRequest().authenticated()
                .and().formLogin()
        ;

    }
}

1.4.3. 自定義ClientDetailsService實現(xiàn)類

import com.exercise.dao.UserDao;
import com.exercise.model.UserDto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UserDao userDao;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        UserDto bean = userDao.getUserByUsername(s);
        if (bean == null)
            return null;
        List<String> permissions = userDao.findPermissionByUserId(bean.getId());
        String[] permissionArray = new String[permissions.size()];
        permissions.toArray(permissionArray);
        UserDetails userDetails = User.withUsername(bean.getUsername()).password(bean.getPassword()).authorities(permissionArray).build();
        return userDetails;
    }
}

1.4.4. 相關(guān)實體類

UserDao: 

import com.exercise.model.PermissionDto;
import com.exercise.model.UserDto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import java.util.ArrayList;
import java.util.List;

@Repository
public class UserDao {


    @Autowired
    JdbcTemplate jdbcTemplate;

    public UserDto getUserByUsername(String usernmae)
    {
        String sql = "select id,username,password,fullname from sys_user where username = ?";
        List<UserDto> result = jdbcTemplate.query(sql, new Object[]{usernmae}, new BeanPropertyRowMapper<>(UserDto.class));
        if (result== null && result.size() <= 0)return null;
        return result.get(0);
    }

    // 根據(jù)用戶id 查詢用戶權(quán)限
    public List<String> findPermissionByUserId(String userId)
    {
        String sql = "select id , permission as code, permission as url, name as description from sys_permission where id in (" +
                "select permission_id from sys_role_permission where role_id in (" +
                "select role_id from sys_role_user where user_id = ?" +
                ")" +
                ")";
        List<PermissionDto> result = jdbcTemplate.query(sql, new Object[]{userId}, new BeanPropertyRowMapper<>(PermissionDto.class));
        List<String> permissions = new ArrayList<String>();
        result.forEach( p->permissions.add(p.getCode()) );
        return permissions;

    }
}

PermissionDto:

import lombok.Data;

@Data
public class PermissionDto {

    private String id;

    private String code;

    private String description;

    private String url;

}

UserDto:

import lombok.Data;

@Data
public class UserDto {

    private String id;

    private String username;

    private String password;

    private String fullname;

    private String phone;

    private String nickName;

}

1.5. gateway 網(wǎng)關(guān)
1.5.1 pom依賴

 <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!-- security-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-jwt</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-oauth2-jose</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!-- actuator-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
        </dependency>
        <!-- test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

1.5.2. application.yml配置文件

#端口
server:
  port: 8205

spring:
  application:
    name: gateway-forward
  cloud:
    gateway:
      routes:
        # 路由標識(id:標識片任,具有唯一性)   簡單嘗試
        - id: auth-server
          uri: http://localhost:8202
          order: 8202
          predicates:
            - Path=/auth/**
          filters:
            - StripPrefix=1
        - id: order-server
          uri: http://localhost:8201
          order: 8201
          predicates:
            - Path=/order/**
          filters:
            - StripPrefix=1
eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:8200/eureka/
  instance:
    instance-id: ${spring.application.name}:${server.port}
    prefer-ip-address: true

logging:
  level:
    # log 級別
    com.xxx: debug

1.5.3. security安全配置

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;

@EnableWebFluxSecurity
@Configuration
public class WebSecurityConfig {

    @Bean
    public SecurityWebFilterChain webFluxFilterChain(ServerHttpSecurity http) {
        http
                .csrf().disable()
                .authorizeExchange()
                .pathMatchers("/**").permitAll()
//               //option 請求默認放行
//                .pathMatchers(HttpMethod.OPTIONS).permitAll()
                .and()
                .formLogin()
        ;

        return http.build();
    }

}

1.5.4. gateway全局過濾器

@Component
@Slf4j
public class AuthFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token = exchange.getRequest().getHeaders().getFirst("Authorization");
        if (!StringUtils.isEmpty(token)) {
            //從token中解析用戶信息并設(shè)置到Header中去
            String realToken = token.replace("Bearer ", "");
            org.springframework.security.jwt.Jwt jwt = JwtHelper.decode(realToken);
            String claims = jwt.getClaims();
            Map<String, Object> map = (Map<String, Object>) JSON.parse(claims);
            //取出用戶身份信息
            String userStr = (String) map.get("user_name");
            JSONArray authoritiesArray = (JSONArray) map.get("authorities");

            log.info("AuthFilter.filter() user:{}",userStr);
            String[] authorities = authoritiesArray.toArray(new String[authoritiesArray.size()]);
            Map<String,Object> jsonToken = new HashMap<>();
            jsonToken.put("principal",userStr);
            jsonToken.put("authorities",authorities);

            //把身份信息和權(quán)限信息放在json中,加入http的header中,轉(zhuǎn)發(fā)給微服務(wù)
            ServerHttpRequest host = exchange.getRequest().mutate().header("json-token", EncryptUtil.encodeUTF8StringBase64(JSON.toJSONString(jsonToken))).build();
            ServerWebExchange build = exchange.mutate().request(host).build();
            return chain.filter(build);
        }


        return chain.filter(exchange);

    }

    @Override
    public int getOrder() {
        return -500;
    }

    protected class JwtUtil implements JwtDecoder {
        @Override
        public Jwt decode(String s) throws JwtException {
            return null;
        }
    }
}

1.5.5. 其他類

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Base64;

public class EncryptUtil {
    private static final Logger logger = LoggerFactory.getLogger(EncryptUtil.class);

    public static String encodeBase64(byte[] bytes){
        String encoded = Base64.getEncoder().encodeToString(bytes);
        return encoded;
    }

    public static byte[]  decodeBase64(String str){
        byte[] bytes = null;
        bytes = Base64.getDecoder().decode(str);
        return bytes;
    }

    public static String encodeUTF8StringBase64(String str){
        String encoded = null;
        try {
            encoded = Base64.getEncoder().encodeToString(str.getBytes("utf-8"));
        } catch (UnsupportedEncodingException e) {
            logger.warn("不支持的編碼格式",e);
        }
        return encoded;

    }

    public static String  decodeUTF8StringBase64(String str){
        String decoded = null;
        byte[] bytes = Base64.getDecoder().decode(str);
        try {
            decoded = new String(bytes,"utf-8");
        }catch(UnsupportedEncodingException e){
            logger.warn("不支持的編碼格式",e);
        }
        return decoded;
    }

    public static String encodeURL(String url) {
        String encoded = null;
        try {
            encoded =  URLEncoder.encode(url, "utf-8");
        } catch (UnsupportedEncodingException e) {
            logger.warn("URLEncode失敗", e);
        }
        return encoded;
    }


    public static String decodeURL(String url) {
        String decoded = null;
        try {
            decoded = URLDecoder.decode(url, "utf-8");
        } catch (UnsupportedEncodingException e) {
            logger.warn("URLDecode失敗", e);
        }
        return decoded;
    }

    public static void main(String [] args){
        String str = "abcd{'a':'b'}";
        String encoded = EncryptUtil.encodeUTF8StringBase64(str);
        String decoded = EncryptUtil.decodeUTF8StringBase64(encoded);
        System.out.println(str);
        System.out.println(encoded);
        System.out.println(decoded);

        String url = "== wo";
        String urlEncoded = EncryptUtil.encodeURL(url);
        String urlDecoded = EncryptUtil.decodeURL(urlEncoded);
        
        System.out.println(url);
        System.out.println(urlEncoded);
        System.out.println(urlDecoded);
    }


}

1.6. 資源服務(wù)器
1.6.1. 依賴配置

pom.xml

<dependencies>
<!--        <dependency>-->
<!--            <groupId>org.springframework.cloud</groupId>-->
<!--            <artifactId>spring-cloud-starter-netflix-eureka-client</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-web</artifactId>
        </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>
            <groupId>javax.interceptor</groupId>
            <artifactId>javax.interceptor-api</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

application.yml
server:
  port: 8201

spring:
  application:
    name: order-server

1.6.2. 資源服務(wù)器配置
繼承ResourceServerConfigurerAdapter類

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    public static final String RESOURCE_ID = "res1";

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/**").access("#oauth2.hasScope('ROLE_API')")
                .and().csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        super.configure(http);
    }
    // 資源服務(wù)令牌解析服務(wù)
    /**
     * 使用遠程服務(wù)請求授權(quán)服務(wù)器校驗的token蔬胯,必須制定校驗token 的url对供, client_id,client_secret
     * @return
     */
//    @Bean
//    public ResourceServerTokenServices tokenServices()
//    {
//        RemoteTokenServices service = new RemoteTokenServices();
//        service.setCheckTokenEndpointUrl("http://localhost:8202/oauth/check_token");
//        service.setClientId("c1");
//        service.setClientSecret("secret");
//        return service;
//    }

    @Autowired
    private TokenStore tokenStore;

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.resourceId(RESOURCE_ID) // 資源id
//                .tokenServices(tokenServices()) // 驗證令牌的服務(wù)
                .tokenStore(tokenStore) // 驗證令牌的服務(wù)
                .stateless(true);
    }
}

安全配置繼承WebSecurityConfigurerAdapter

@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/r/**").authenticated()
                .anyRequest().permitAll();
    }
}

1.6.3 token過濾器
獲取從網(wǎng)關(guān)處轉(zhuǎn)發(fā)的token,填充到認證的安全上下文中笔宿,實現(xiàn)身份權(quán)限識別

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.exercise.model.UserDTO;
import com.exercise.util.EncryptUtil;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class TokenAutnenticationFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        //解析出頭中的token
        String token = httpServletRequest.getHeader("json-token");
        if(token!=null){
            String json = EncryptUtil.decodeUTF8StringBase64(token);
            //將token轉(zhuǎn)成json對象
            JSONObject jsonObject = JSON.parseObject(json);
            //用戶身份信息
            UserDTO userDTO = new UserDTO();
            String principal = jsonObject.getString("principal");
            userDTO.setUsername(principal);
//            UserDTO userDTO = JSON.parseObject(jsonObject.getString("principal"), UserDTO.class);
            //用戶權(quán)限
            JSONArray authoritiesArray = jsonObject.getJSONArray("authorities");
            String[] authorities = authoritiesArray.toArray(new String[authoritiesArray.size()]);
            //將用戶信息和權(quán)限填充 到用戶身份token對象中
            UsernamePasswordAuthenticationToken authenticationToken
                    = new UsernamePasswordAuthenticationToken(userDTO,null, AuthorityUtils.createAuthorityList(authorities));
            authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
            //將authenticationToken填充到安全上下文
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);


        }
        filterChain.doFilter(httpServletRequest,httpServletResponse);
    }
}

使用zuul作為網(wǎng)關(guān)

  1. 設(shè)置依賴和配置
pom.xml
<dependencies>
<!--        <dependency>-->
<!--            <groupId>org.springframework.cloud</groupId>-->
<!--            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>-->
<!--        </dependency>-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>com.netflix.hystrix</groupId>
            <artifactId>hystrix-javanica</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.retry</groupId>
            <artifactId>spring-retry</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-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </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>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-jwt</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.interceptor</groupId>
            <artifactId>javax.interceptor-api</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

application.yml
#端口
server:
  port: 8204

spring:
  application:
    name: zuul-server
zuul:
  routes:
    order-server:
      path: /order/**
      url: http://localhost:8201/
    user-server:
      path: /auth/**
      url: http://localhost:8202/
  retryable: true
  add-host-header: true
  #不過濾HTTP請求頭信息
  sensitive-headers:
  1. 配置資源服務(wù)
    2.1. 資源服務(wù)配置
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;

@Configuration
public class ResourceServerConfig {
    public static final String RESOURCE_ID = "res1";

    //user資源服務(wù)配置
    @Configuration
    @EnableResourceServer
    public class UAAServerConfig extends ResourceServerConfigurerAdapter {
        @Autowired
        private TokenStore tokenStore;

        @Override
        public void configure(ResourceServerSecurityConfigurer resources){
            resources.tokenStore(tokenStore).resourceId(RESOURCE_ID)
                    .stateless(true);
        }

        @Override
        public void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .antMatchers("/auth/**").permitAll();
        }
    }

    //order資源
    //uaa資源服務(wù)配置
    @Configuration
    @EnableResourceServer
    public class OrderServerConfig extends ResourceServerConfigurerAdapter {
        @Autowired
        private TokenStore tokenStore;

        @Override
        public void configure(ResourceServerSecurityConfigurer resources){
            resources.tokenStore(tokenStore).resourceId(RESOURCE_ID)
                    .stateless(true);
        }

        @Override
        public void configure(HttpSecurity http) throws Exception {
            http
                    .authorizeRequests()
                    .antMatchers("/order/**").access("#oauth2.hasScope('ROLE_API')");
        }
    }


    //配置其它的資源服務(wù)..
}

2.2. 安全配置

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http
                .authorizeRequests()
                .antMatchers("/**").permitAll()
                .and().csrf().disable();
    }
}

2.3. 其他配置

@Configuration
public class ZuulConfig {

    @Bean
    public AuthFilter preFileter() {
        return new AuthFilter();
    }

    @Bean
    public FilterRegistrationBean corsFilter() {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        final CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        config.setMaxAge(18000L);
        source.registerCorsConfiguration("/**", config);
        CorsFilter corsFilter = new CorsFilter(source);
        FilterRegistrationBean bean = new FilterRegistrationBean(corsFilter);
        bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
        return bean;
    }
}

2.4. 身份信息過濾

import com.alibaba.fastjson.JSON;
import com.exercise.util.EncryptUtil;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class AuthFilter extends ZuulFilter {

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 0;
    }

    @Override
    public Object run() throws ZuulException {
        RequestContext ctx = RequestContext.getCurrentContext();
        //從安全上下文中拿 到用戶身份對象
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if(!(authentication instanceof OAuth2Authentication)){
            return null;
        }
        OAuth2Authentication oAuth2Authentication = (OAuth2Authentication) authentication;
        Authentication userAuthentication = oAuth2Authentication.getUserAuthentication();
        //取出用戶身份信息
        String principal = userAuthentication.getName();
        //取出用戶權(quán)限
        List<String> authorities = new ArrayList<>();
        //從userAuthentication取出權(quán)限辐脖,放在authorities
        userAuthentication.getAuthorities().stream().forEach(c->authorities.add(((GrantedAuthority) c).getAuthority()));

        OAuth2Request oAuth2Request = oAuth2Authentication.getOAuth2Request();
        Map<String, String> requestParameters = oAuth2Request.getRequestParameters();
        Map<String,Object> jsonToken = new HashMap<>(requestParameters);
        if(userAuthentication!=null){
            jsonToken.put("principal",principal);
            jsonToken.put("authorities",authorities);
        }

        //把身份信息和權(quán)限信息放在json中,加入http的header中,轉(zhuǎn)發(fā)給微服務(wù)
        ctx.addZuulRequestHeader("json-token", EncryptUtil.encodeUTF8StringBase64(JSON.toJSONString(jsonToken)));

        return null;
    }
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載雕崩,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者厦酬。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市炬灭,隨后出現(xiàn)的幾起案子醋粟,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件米愿,死亡現(xiàn)場離奇詭異厦凤,居然都是意外死亡,警方通過查閱死者的電腦和手機育苟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進店門较鼓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人违柏,你說我怎么就攤上這事博烂。” “怎么了漱竖?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵禽篱,是天一觀的道長。 經(jīng)常有香客問我馍惹,道長躺率,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任万矾,我火速辦了婚禮悼吱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘勤众。我一直安慰自己舆绎,他們只是感情好,可當我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布们颜。 她就那樣靜靜地躺著吕朵,像睡著了一般。 火紅的嫁衣襯著肌膚如雪窥突。 梳的紋絲不亂的頭發(fā)上努溃,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天,我揣著相機與錄音阻问,去河邊找鬼梧税。 笑死,一個胖子當著我的面吹牛称近,可吹牛的內(nèi)容都是我干的第队。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼刨秆,長吁一口氣:“原來是場噩夢啊……” “哼凳谦!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起衡未,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤尸执,失蹤者是張志新(化名)和其女友劉穎家凯,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體如失,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡绊诲,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了褪贵。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片掂之。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖竭鞍,靈堂內(nèi)的尸體忽然破棺而出板惑,到底是詐尸還是另有隱情,我是刑警寧澤偎快,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站洽胶,受9級特大地震影響晒夹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜姊氓,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一丐怯、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧翔横,春花似錦读跷、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至荡短,卻和暖如春丐枉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背掘托。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工瘦锹, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人闪盔。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓弯院,卻偏偏與公主長得像,于是被迫代替她去往敵國和親泪掀。 傳聞我的和親對象是個殘疾皇子听绳,可洞房花燭夜當晚...
    茶點故事閱讀 43,486評論 2 348