原文地址:https://blog.lanweihong.com/p...

OAuth 是一种用来标准令牌(Token)发放的受权机制,次要蕴含了四种受权模式:受权码模式、简化模式、明码模式和客户端模式。对于 OAuth 更多介绍可拜访 了解OAuth 2.0 查看。本文次要以 明码模式 来实现用户认证和受权。

搭建我的项目

我的项目代码已上传至 Github 。

本例我的项目以微服务为根底,仅实现认证服务和资源服务,其余如网关、服务治理、配置核心等省略,本文重点是应用 Spring Security + OAuth 2.0 + JWT 实现用户认证受权。

我的项目构造如下图,认证服务和资源服务拆散,认证服务次要是提供令牌和校验令牌服务。

父工程 pom.xml 配置如下,次要是指定依赖包的版本:

    <!-- 依赖包版本治理 -->    <properties>        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>        <spring.boot.version>2.2.2.RELEASE</spring.boot.version>        <spring.cloud.version>Hoxton.SR9</spring.cloud.version>        <spring.cloud.alibaba.version>2.2.1.RELEASE</spring.cloud.alibaba.version>        <mysql.driver.version>8.0.16</mysql.driver.version>        <lombok.version>1.16.18</lombok.version>        <druid.version>1.1.10</druid.version>    </properties>    <dependencyManagement>        <dependencies>            <!-- spring boot 2.2.2.RELEASE -->            <dependency>                <groupId>org.springframework.boot</groupId>                <artifactId>spring-boot-dependencies</artifactId>                <version>2.2.2.RELEASE</version>                <type>pom</type>                <scope>import</scope>            </dependency>            <!-- spring cloud -->            <dependency>                <groupId>org.springframework.cloud</groupId>                <artifactId>spring-cloud-dependencies</artifactId>                <version>${spring.cloud.version}</version>                <type>pom</type>                <scope>import</scope>            </dependency>            <!-- Spring Cloud Alibaba -->            <dependency>                <groupId>com.alibaba.cloud</groupId>                <artifactId>spring-cloud-alibaba-dependencies</artifactId>                <version>${spring.cloud.alibaba.version}</version>                <type>pom</type>                <scope>import</scope>            </dependency>            <!-- mysql driver -->            <dependency>                <groupId>mysql</groupId>                <artifactId>mysql-connector-java</artifactId>                <version>${mysql.driver.version}</version>            </dependency>            <!-- lombok -->            <dependency>                <groupId>org.projectlombok</groupId>                <artifactId>lombok</artifactId>                <version>${lombok.version}</version>            </dependency>            <!-- druid -->            <dependency>                <groupId>com.alibaba</groupId>                <artifactId>druid-spring-boot-starter</artifactId>                <version>${druid.version}</version>            </dependency>        </dependencies>    </dependencyManagement>

搭建认证服务

引入依赖

  1. pom.xml 引入以下依赖:
<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>com.alibaba</groupId>    <artifactId>druid-spring-boot-starter</artifactId></dependency><dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency>    <groupId>mysql</groupId>    <artifactId>mysql-connector-java</artifactId></dependency>

spring-cloud-starter-oauth2 曾经蕴含了 spring-cloud-starter-securityspring-security-oauth2spring-security-jwt 这3个依赖,只需引入 spring-cloud-starter-oauth2 即可。

  1. 编辑 application.yml,增加数据库连贯参数:
spring:  datasource:    type: com.alibaba.druid.pool.DruidDataSource    url: jdbc:mysql://127.0.0.1:3307/oauth_server?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=Asia/Shanghai    username: root    password: 1    druid:      driver-class-name: com.mysql.cj.jdbc.Driver      initial-size: 5      max-active: 50      max-wait: 60000      min-idle: 5

筹备工作

  1. 新建 UserDTO 类,实现 org.springframework.security.core.userdetails.UserDetails 接口;
/** * @author lanweihong 986310747@qq.com */@Datapublic class UserDTO implements Serializable, UserDetails {    private static final long serialVersionUID = 5538522337801286424L;    private String userName;    private String password;    private Set<SimpleGrantedAuthority> authorities;    public Collection<? extends GrantedAuthority> getAuthorities() {        return this.authorities;    }    public String getPassword() {        return this.password;    }    public String getUsername() {        return this.userName;    }    public boolean isAccountNonExpired() {        return true;    }    public boolean isAccountNonLocked() {        return true;    }    public boolean isCredentialsNonExpired() {        return true;    }    public boolean isEnabled() {        return true;    }}
  1. 新建类 UserDetailsServiceImpl,实现 org.springframework.security.core.userdetails.UserDetailsService 接口,用于校验用户凭据。
@Servicepublic class UserDetailsServiceImpl implements UserDetailsService {    private PasswordEncoder passwordEncoder;    @Autowired    public void setPasswordEncoder(PasswordEncoder passwordEncoder) {        this.passwordEncoder = passwordEncoder;    }    @Override    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {        // TODO 理论开发中,这里请批改从数据库中查问...        UserDTO user = new UserDTO();        user.setUserName(username);        // 明码为 123456 ,且加密        user.setPassword(passwordEncoder.encode("123456"));        return user;    }}

以上用户配置用于测试,任意用户名,但明码为 123456,理论生产中务必批改为从数据库中读取校验。

配置认证受权服务器

  1. 新建类 Oauth2ServerConfig,继承 org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter 类;在 Oauth2ServerConfig 类上 增加注解 @EnableAuthorizationServer

    框架提供了几个默认的端点:

    • /oauth/authorize:受权端点
    • /oauth/token:获取令牌端点
    • /oauth/confirm_access:用户确认受权端点
    • /oauth/check_token:校验令牌端点
    • /oauth/error:用于在受权服务器中出现谬误
    • /oauth/token_key:获取 jwt 公钥端点
  2. 继承 AuthorizationServerConfigurerAdapter 类后,咱们须要重写以下三个办法扩大实现咱们的需要。

    • configure(ClientDetailsServiceConfigurer clients) :用于定义、初始化客户端信息
    • configure(AuthorizationServerEndpointsConfigurer endpoints):用于定义受权令牌端点及服务
    • configure(AuthorizationServerSecurityConfigurer security):用于定义令牌端点的平安束缚

配置客户端详细信息

ClientDetailsServiceConfigurer 用于定义 内存 中或 基于JDBC存储实现 的客户端,其重要的几个属性有:

  • clientId:客户端id,必填;
  • clientSecret:客户端密钥;
  • authorizedGrantTypes:客户端受权类型,有 5 种模式: authorization_codepasswordclient_credentialsimplicitrefresh_token
  • scope:受权范畴;
  • accessTokenValiditySecondsaccess_token 无效工夫,单位为秒,默认为 12 小时;
  • refreshTokenValiditySecondsrefresh_token 无效工夫,单位为秒,默认为 30 天;

客户端信息个别保留在 Redis 或 数据库中,本例中客户端信息保留在 MySQL 中;
基于JDBC存储 模式须要创立数据表,官网提供了建表的 SQL 语句,可拜访 schema.sql 获取 SQL ;

  1. 应用以下 SQL(实用于MySQL) 来建表:
CREATE TABLE `oauth_client_details`  (  `client_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,  `resource_ids` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,  `client_secret` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,  `scope` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,  `authorized_grant_types` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,  `web_server_redirect_uri` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,  `authorities` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,  `access_token_validity` int(11) NULL DEFAULT NULL,  `refresh_token_validity` int(11) NULL DEFAULT NULL,  `additional_information` varchar(4096) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,  `autoapprove` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,  PRIMARY KEY (`client_id`) USING BTREE) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
  1. 增加一条客户端信息用于测试:
INSERT INTO `oauth_client_details` VALUES ('auth-server', NULL, '$2a$10$mcEwJ8qqhk2DYIle6VfhEOZHRdDbCSizAQbIwBR7tTuv9Q7Fca9Gi', 'all', 'password,refresh_token', '', NULL, NULL, NULL, NULL, NULL);

其中明码 123456 应用 BCryptPasswordEncoder 加密,加密后字符为 $2a$10$mcEwJ8qqhk2DYIle6VfhEOZHRdDbCSizAQbIwBR7tTuv9Q7Fca9Gi

  1. 配置 ClientDetailsServiceConfigurer ,指定客户端信息:
@Configuration@EnableAuthorizationServerpublic class Oauth2ServerConfig extends AuthorizationServerConfigurerAdapter {    private final DataSource dataSource;        private final PasswordEncoder passwordEncoder;    @Autowired    public Oauth2ServerConfig(DataSource dataSource, PasswordEncoder passwordEncoder) {        this.dataSource = dataSource;        this.passwordEncoder = passwordEncoder;    }    @Override    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {        // 应用基于 JDBC 存储模式        JdbcClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);        // client_secret 加密        clientDetailsService.setPasswordEncoder(passwordEncoder);        clients.withClientDetails(clientDetailsService);    }}

配置受权令牌端点及服务

配置 AuthorizationServerEndpointsConfigurer 须要指定 AuthenticationManagerUserDetailService,尤其是应用明码模式时,必须指定 AuthenticationManager,否则会报 Unsupported grant type: password 谬误。

  1. 新建 WebSecurityConfig 类,继承 org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter 类,重写 authenticationManagerBean() 办法,并定义须要用到的 PasswordEncoder
@Configuration@EnableWebSecurity@EnableGlobalMethodSecurity(prePostEnabled = true)public class WebSecurityConfig extends WebSecurityConfigurerAdapter {    @Override    protected void configure(HttpSecurity http) throws Exception {        http                // 反对跨域申请                .cors()                .and()                // 禁用 CSRF                .csrf().disable()                .formLogin().disable()                .httpBasic().disable()                .logout().disable()                .authorizeRequests()                .antMatchers("/oauth/token").permitAll();                .anyRequest().authenticated();    }    /**     * 重写 authenticationManagerBean()     * @return     * @throws Exception     */    @Override    @Bean    public AuthenticationManager authenticationManagerBean() throws Exception {        return super.authenticationManagerBean();    }        @Bean    public PasswordEncoder passwordEncoder() {        return new BCryptPasswordEncoder();    }}
  1. 配置 AuthorizationServerEndpointsConfigurer
@Configuration@EnableAuthorizationServerpublic class Oauth2ServerConfig extends AuthorizationServerConfigurerAdapter {    private final UserDetailsServiceImpl userDetailsService;    /**     * 明码模式 grant_type:password 需指定 AuthenticationManager     */    private final AuthenticationManager authenticationManager;    @Autowired    public Oauth2ServerConfig(UserDetailsServiceImpl userDetailsService,                              AuthenticationManager authenticationManager) {        this.userDetailsService = userDetailsService;        this.authenticationManager = authenticationManager;    }    @Override    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {        endpoints                // 开启明码模式受权                .authenticationManager(authenticationManager)                .userDetailsService(userDetailsService);    }}
应用 JWT 作为令牌格局
生成 JWT 密钥对

应用 JDK 的 keytool 工具生成 JKS 密钥对 jwt.jks,并将 jwt.jks 放到 resources 目录下。

定位至 JDK 目录下的 bin 目录,执行以下命令生成密钥对,记住口令密钥,代码中须要用到密钥来读取密钥对,以下命令以 123456 为例:

keytool -genkey -alias weihong -keyalg RSA -keypass 123456 -keystore jwt.jks -storepass 123456

参数阐明:

-genkey 生成密钥-alias 别名-keyalg 密钥算法-keypass 密钥口令-keystore 生成密钥对的存储门路和名称-storepass 密钥对口令
定义 token 转换器

Oauth2ServerConfig 类中定义 accessTokenConverter()keyPair()

    /**     * token 转换器     * 默认是 uuid 格局,咱们在这里指定 token 格局为 jwt     * 应用非对称加密算法对 token 签名     * @return     */    @Bean    public JwtAccessTokenConverter accessTokenConverter() {        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();        // 应用非对称加密算法对 token 签名        converter.setKeyPair(keyPair());        return converter;    }    @Bean    public KeyPair keyPair() {        // 从 classpath 目录下的证书 jwt.jks 中获取秘钥对,输出在以上生成密钥对设置的明码: 123456,这里应用硬编码,倡议写到配置文件中        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "123456".toCharArray());        return keyStoreKeyFactory.getKeyPair("weihong", "123456".toCharArray());    }
指定令牌存储策略为 JWT

配置 AuthorizationServerEndpointsConfigurer 的令牌存储策略为 JWT,指定 accessTokenConverter 为咱们定义好的 accessTokenConverter()

@Configuration@EnableAuthorizationServerpublic class Oauth2ServerConfig extends AuthorizationServerConfigurerAdapter {    private final UserDetailsServiceImpl userDetailsService;    /**     * 明码模式 grant_type:password 需指定 AuthenticationManager     */    private final AuthenticationManager authenticationManager;    @Autowired    public Oauth2ServerConfig(UserDetailsServiceImpl userDetailsService,                              AuthenticationManager authenticationManager) {        this.userDetailsService = userDetailsService;        this.authenticationManager = authenticationManager;    }    @Override    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {        endpoints                // 开启明码模式受权                .authenticationManager(authenticationManager)                .userDetailsService(userDetailsService)                // 指定令牌存储策略                .accessTokenConverter(accessTokenConverter());    }    /**     * token 转换器     * 默认是 uuid 格局,咱们在这里指定 token 格局为 jwt     * @return     */    @Bean    public JwtAccessTokenConverter accessTokenConverter() {        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();        // 应用非对称加密算法对 token 签名        converter.setKeyPair(keyPair());        return converter;    }    @Bean    public KeyPair keyPair() {        // 从 classpath 目录下的证书 jwt.jks 中获取秘钥对        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "123456".toCharArray());        return keyStoreKeyFactory.getKeyPair("weihong", "123456".toCharArray());    }}    
扩大 JWT 存储内容

有时候咱们须要扩大 JWT 存储的内容,比方存储一些用户数据、权限信息等。咱们能够定义 TokenEnhancer 或继承 TokenEnhancer 来实现 JWT 内容增强器:

    @Bean    public TokenEnhancer tokenEnhancer() {        return (oAuth2AccessToken, oAuth2Authentication) -> {            Map<String, Object> map = new HashMap<>(1);            UserDTO userDTO = (UserDTO) oAuth2Authentication.getPrincipal();            map.put("userName", userDTO.getUsername());            // TODO 其余信息能够自行添加            ((DefaultOAuth2AccessToken) oAuth2AccessToken).setAdditionalInformation(map);            return oAuth2AccessToken;        };    }

配置 AuthorizationServerEndpointsConfigurer JWT 内容增强器:

@Configuration@EnableAuthorizationServerpublic class Oauth2ServerConfig extends AuthorizationServerConfigurerAdapter {    private final UserDetailsServiceImpl userDetailsService;    private final AuthenticationManager authenticationManager;    @Autowired    public Oauth2ServerConfig(UserDetailsServiceImpl userDetailsService,                              AuthenticationManager authenticationManager) {        this.userDetailsService = userDetailsService;        this.authenticationManager = authenticationManager;    }    @Override    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {        TokenEnhancerChain enhancerChain = new TokenEnhancerChain();        List<TokenEnhancer> delegates = new ArrayList<>();        delegates.add(tokenEnhancer());        delegates.add(accessTokenConverter());        // 配置 JWT 内容加强        enhancerChain.setTokenEnhancers(delegates);        endpoints                // 开启明码模式受权                .authenticationManager(authenticationManager)                .userDetailsService(userDetailsService)                .accessTokenConverter(accessTokenConverter())                .tokenEnhancer(enhancerChain);    }        /**     * token 转换器     * 默认是 uuid 格局,咱们在这里指定 token 格局为 jwt     * @return     */    @Bean    public JwtAccessTokenConverter accessTokenConverter() {        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();        // 应用非对称加密算法对 token 签名        converter.setKeyPair(keyPair());        return converter;    }    @Bean    public KeyPair keyPair() {        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "123456".toCharArray());        return keyStoreKeyFactory.getKeyPair("weihong", "123456".toCharArray());    }        /**     * JWT 内容增强器,用于扩大 JWT 内容,能够保留用户数据     * @return     */    @Bean    public TokenEnhancer tokenEnhancer() {        return (oAuth2AccessToken, oAuth2Authentication) -> {            Map<String, Object> map = new HashMap<>(1);            UserDTO userDTO = (UserDTO) oAuth2Authentication.getPrincipal();            map.put("userName", userDTO.getUsername());            // TODO 其余信息能够自行添加            ((DefaultOAuth2AccessToken) oAuth2AccessToken).setAdditionalInformation(map);            return oAuth2AccessToken;        };    }}
应用 Redis 存储 token
  1. pom.xml 中增加依赖:
<dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-data-redis</artifactId></dependency>
  1. 编辑 application.yml,增加 Redis 连贯参数:
spring:  redis:    host: localhost    port: 6379    password: 1
  1. 增加 token 保留至 redis 的配置:
@Configurationpublic class RedisTokenStoreConfig {    @Resource    private RedisConnectionFactory connectionFactory;    @Bean    public TokenStore redisTokenStore() {        return new RedisTokenStore(connectionFactory);    }}
  1. 在认证服务配置中指定 token 存储形式:
@Configuration@EnableAuthorizationServerpublic class Oauth2ServerConfig extends AuthorizationServerConfigurerAdapter {    private final UserDetailsServiceImpl userDetailsService;    /**     * 明码模式 grant_type:password 需指定 AuthenticationManager     */    private final AuthenticationManager authenticationManager;        private final TokenStore tokenStore;    @Autowired    public Oauth2ServerConfig(UserDetailsServiceImpl userDetailsService,                              AuthenticationManager authenticationManager,                              @Qualifier("redisTokenStore") TokenStore tokenStore) {        this.userDetailsService = userDetailsService;        this.authenticationManager = authenticationManager;        this.tokenStore = tokenStore;    }    @Override    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {        endpoints                // 开启明码模式受权                .authenticationManager(authenticationManager)                .userDetailsService(userDetailsService)                // 设置 token 存储形式                .tokenStore(tokenStore);    }}

配置受权令牌平安束缚

    @Override    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {        security                // 容许表单认证                .allowFormAuthenticationForClients()                // 凋谢 /oauth/token_key 获取 token 加密公钥                .tokenKeyAccess("permitAll()")                // 凋谢 /oauth/check_token                .checkTokenAccess("permitAll()");    }

认证受权服务配置残缺代码

Oauth2ServerConfig

@Configuration@EnableAuthorizationServerpublic class Oauth2ServerConfig extends AuthorizationServerConfigurerAdapter {    /**     * 数据源     */    private final DataSource dataSource;    private final UserDetailsServiceImpl userDetailsService;    /**     * 明码模式 grant_type:password 需指定 AuthenticationManager     */    private final AuthenticationManager authenticationManager;    private final PasswordEncoder passwordEncoder;    private final TokenStore tokenStore;    @Autowired    public Oauth2ServerConfig(DataSource dataSource,                              UserDetailsServiceImpl userDetailsService,                              AuthenticationManager authenticationManager,                              PasswordEncoder passwordEncoder,                              @Qualifier("redisTokenStore") TokenStore tokenStore) {        this.dataSource = dataSource;        this.userDetailsService = userDetailsService;        this.authenticationManager = authenticationManager;        this.passwordEncoder = passwordEncoder;        this.tokenStore = tokenStore;    }    @Override    public void configure(AuthorizationServerSecurityConfigurer security) {        security                // 容许表单认证                .allowFormAuthenticationForClients()                // 需通过认证后能力拜访 /oauth/token_key 获取 token 加密公钥                .tokenKeyAccess("permitAll()")                // 凋谢 /oauth/check_token                .checkTokenAccess("permitAll()");    }    @Override    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {        // 应用基于 JDBC 存储模式        JdbcClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);        clientDetailsService.setPasswordEncoder(passwordEncoder);        clients.withClientDetails(clientDetailsService);    }    @Override    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {        TokenEnhancerChain enhancerChain = new TokenEnhancerChain();        List<TokenEnhancer> delegates = new ArrayList<>();        delegates.add(tokenEnhancer());        delegates.add(accessTokenConverter());        // 配置 JWT 内容加强        enhancerChain.setTokenEnhancers(delegates);        endpoints                // 开启明码模式受权                .authenticationManager(authenticationManager)                .userDetailsService(userDetailsService)                .accessTokenConverter(accessTokenConverter())                .tokenEnhancer(enhancerChain)                // 设置 token 存储形式                .tokenStore(tokenStore);    }    /**     * token 转换器     * 默认是 uuid 格局,咱们在这里指定 token 格局为 jwt     * @return     */    @Bean    public JwtAccessTokenConverter accessTokenConverter() {        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();        // 应用非对称加密算法对 token 签名        converter.setKeyPair(keyPair());        return converter;    }    @Bean    public KeyPair keyPair() {        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "123456".toCharArray());        return keyStoreKeyFactory.getKeyPair("weihong", "123456".toCharArray());    }    /**     * JWT 内容增强器,用于扩大 JWT 内容,能够保留用户数据     * @return     */    @Bean    public TokenEnhancer tokenEnhancer() {        return (oAuth2AccessToken, oAuth2Authentication) -> {            Map<String, Object> map = new HashMap<>(1);            UserDTO userDTO = (UserDTO) oAuth2Authentication.getPrincipal();            map.put("userName", userDTO.getUsername());            // TODO 其余信息能够自行添加            ((DefaultOAuth2AccessToken) oAuth2AccessToken).setAdditionalInformation(map);            return oAuth2AccessToken;        };    }}

WebSecurityConfig

@Configuration@EnableWebSecurity@EnableGlobalMethodSecurity(prePostEnabled = true)public class WebSecurityConfig extends WebSecurityConfigurerAdapter {    @Override    protected void configure(HttpSecurity http) throws Exception {        http                // 反对跨域申请                .cors()                .and()                // 禁用 CSRF                .csrf().disable()                .formLogin().disable()                .httpBasic().disable()                .logout().disable()                .authorizeRequests()                .antMatchers("/oauth/token").permitAll()                .anyRequest().authenticated();    }    /**     * 重写 authenticationManagerBean()     * @return     * @throws Exception     */    @Override    @Bean    public AuthenticationManager authenticationManagerBean() throws Exception {        return super.authenticationManagerBean();    }    @Bean    public PasswordEncoder passwordEncoder() {        return new BCryptPasswordEncoder();    }}

测试获取 token

获取 token

运行我的项目,应用 Postman 拜访 /oauth/token 端点,并传参数,参数必须与咱们配置的内容统一;

胜利获取到 token ,格局如下:

{    "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MTA1NjkzNDksInVzZXJOYW1lIjoiYWRtaW4iLCJ1c2VyX25hbWUiOiJhZG1pbiIsImp0aSI6IjgzM2VjZDdkLThmMzctNDAxOS04YWQwLTBlODI3ZTM4M2U5YyIsImNsaWVudF9pZCI6Im9hdXRoLXNlcnZlciIsInNjb3BlIjpbImFsbCJdfQ.ke6fWfGMOXhppF-6XXftZJx0w8hSnTKYYwvi_As66Ats9_AFqrHCZiuHA_M5LD2bJzahFC__-IUr_6g6ajx-IlLpSPqs3izgbuOPcTzCivfznGn38W5kYPe1ygQ8mJzN97yAT1QKZGMAT0nr7HR5NSG2MHYPbHuWSHp4KVIf7XQbszmXVPKEeQsv64QZ8O1xe9XtshF4mtZsxfLEGxAZEPSkoyJi-vwH6qKnvVh8EI8zgwTX5cIh6Gj4rcEfDiJYNAiI_NanuNA1wBoI1eD-QYSUQ5XXW1Q4vQAnjQMQwvTZYn1hGdAbeHQrA9hPLw5_Axeq8_meWpNobla_rRYkLQ",    "token_type": "bearer",    "refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbImFsbCJdLCJhdGkiOiI4MzNlY2Q3ZC04ZjM3LTQwMTktOGFkMC0wZTgyN2UzODNlOWMiLCJleHAiOjE2MTMxMTgxNDksInVzZXJOYW1lIjoiYWRtaW4iLCJqdGkiOiIwMDljZjhmNy05OTE5LTQyODEtYjUxNS02NjM3ZjIyM2MyN2YiLCJjbGllbnRfaWQiOiJvYXV0aC1zZXJ2ZXIifQ.bFMQRXCOz2rvu8QhTOjjlM66Fe3EM5F2wUXI-3dQOxnu2AOCsCJKUZdT0AhsnJkSI5Ewc1jUd7TiUifj9p6CYzIuHtnPORUUE67vt7eiKjpdNdNaUIvXzSoAcx-B5FgYynKslZm5S6WwqQMEb6jFMeg1iN3DphDPbjUMCP2qZevm6fNTT0b7PzxE0POepqqEnyjIS1YOnMnyHkgSAQCtYMAwWATalS4tMFNRb-hbE2MGi-U1j3Z1Mq79x9Uce8ZXjD2a_sCE9x0fqTixO-pRUrQNrIqiX_bZlw96xktnUQy2wCoJiZRxKjZyRhPLxOQPR7FUyd8yFXjCHR_yf5mwYw",    "expires_in": 43199,    "scope": "all",    "userName": "admin",    "jti": "833ecd7d-8f37-4019-8ad0-0e827e383e9c"}

将返回的 token 复制到 https://jwt.io/ 解析,发现已正确解析。

校验 token

  1. 应用 Postman 拜访 /oauth/check_token 端点,咱们试着增加谬误的 token ,而后发送申请校验,发现返回谬误;

  1. 应用 Postman 拜访 /oauth/check_token 端点,咱们应用正确的 token 校验,胜利返回信息;

刷新 token

应用 Postman 拜访 /oauth/token 端点,其中参数 grant_type 应用 refresh_tokenrefresh_token 内容为咱们从 /oauth/token 获取的 refresh_token留神不是 access_token);其余参数请自行配置,可参考如下:

搭建资源服务

  1. 新建 module ,在 pom.xml 中增加依赖:
<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>
  1. application.yml 中增加 token 校验的相干参数,将 token 校验地址改为认证服务的 token 校验地址
oauth2:  resource-id: resource-server  # token 校验地址  check-token-url: http://127.0.0.1:8089/oauth/check_token  client-id: oauth-server  client-secret: 123456

配置资源服务

  1. 新建 ResourceServerConfig 类,继承 org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter,增加注解 @EnableResourceServer,并配置 token 校验服务;
@Configuration@EnableResourceServerpublic class ResourceServerConfig extends ResourceServerConfigurerAdapter {    @Value("${oauth2.check-token-url}")    private String checkTokenUrl;    @Value("${oauth2.resource-id}")    private String resourceId;    @Value("${oauth2.client-id}")    private String clientId;    @Value("${oauth2.client-secret}")    private String clientSecret;    @Override    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {        resources.resourceId(resourceId).stateless(true);        resources.tokenServices(resourceServerTokenServices());    }    @Override    public void configure(HttpSecurity http) throws Exception {        http.authorizeRequests()                .requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll()                .anyRequest()                .authenticated()                .and()                .requestMatchers()                .antMatchers("/users/**");    }    /**     * 配置 token 校验服务     * @return     */    @Bean    ResourceServerTokenServices resourceServerTokenServices() {        RemoteTokenServices remoteTokenServices = new RemoteTokenServices();        remoteTokenServices.setCheckTokenEndpointUrl(checkTokenUrl);        remoteTokenServices.setClientId(clientId);        remoteTokenServices.setClientSecret(clientSecret);        remoteTokenServices.setAccessTokenConverter(accessTokenConverter());        return remoteTokenServices;    }    @Bean    public AccessTokenConverter accessTokenConverter() {        return new DefaultAccessTokenConverter();    }}
  1. 增加 Controller
@RestControllerpublic class HomeController {    @GetMapping("/users")    public Map<String, Object> test(Authentication authentication) {        Map<String, Object> data = new HashMap<>(1);        data.put("user", authentication.getPrincipal());        return data;    }}

测试

应用 Postman 拜访 /users ,返回未受权谬误:

应用 Postman 拜访 /users,带上 token 拜访,胜利申请并获取到用户数据;

我的项目源码地址

https://github.com/lanweihong/spring-securuty-oauth2-jwt

参考文档

  1. 了解OAuth 2.0
  2. OAuth 2 Developers Guide
  3. Spring Security OAuth2自定义令牌配置