• Spring Security OAuth2搭建认证授权中心、资源服务中心、并结合网关校验的完整详细示例



    )

    认证授权中心

    添加依赖

    	 <!-- spring web -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <!-- spring data redis -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
            </dependency>
            <!-- mybatis -->
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>2.1.3</version>
            </dependency>
            <!-- hutool -->
            <dependency>
                <groupId>cn.hutool</groupId>
                <artifactId>hutool-all</artifactId>
            </dependency>
            <!-- mysql -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <scope>runtime</scope>
            </dependency>
            <!-- spring cloud security -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-security</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.security</groupId>
                <artifactId>spring-security-core</artifactId>
            </dependency>
            <!-- spring cloud oauth2 -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-oauth2</artifactId>
            </dependency>
            
          <dependencyManagement>
            <dependencies>
                <!-- spring cloud 依赖 -->
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>Hoxton.SR8</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    application.yml配置

    server:
      port: 8888 # 端口
    
    spring:
      application:
        name: oauth2-server # 应用名
      # 数据库
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        username: root
        password: 123456
        url: jdbc:mysql://127.0.0.1:3306/demo?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useUnicode=true&useSSL=false
      # Redis
      redis:
        port: 6379
        host: 127.0.0.1
        timeout: 3000
        database: 1
        password:
    
    # Oauth2
    client:
      oauth2:
        client-id: appId # 客户端标识 ID
        secret: 123456 # 客户端安全码
        # 授权类型
        grant_types:
          - password
          - refresh_token
        # token 有效时间,单位秒
        token-validity-time: 2592000
        refresh-token-validity-time: 2592000
        # 客户端访问范围
        scopes:
          - api
          - all
    
    # Mybatis
    mybatis:
      configuration:
        map-underscore-to-camel-case: true # 开启驼峰映射
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    Security配置

    配置使用Redis存储Token信息  
    
    配置密码的加密、解密、校验逻辑 
     
    初始化认证管理对象   
    
    配置请求访问的放行和认证规则
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    import cn.hutool.crypto.digest.DigestUtil;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
    import javax.annotation.Resource;
    
    /**
     * Security配置
     */
    @Configuration
    @EnableWebSecurity
    public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    
        /**
         * 注入Redis连接工厂
         */
        @Resource
        private RedisConnectionFactory redisConnectionFactory;
    
        /**
         * 初始化RedisTokenStore,用于将token存储至Redis
         *
         * @return
         */
        @Bean
        public RedisTokenStore redisTokenStore() {
            RedisTokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory);
            // 设置key的层级前缀
            redisTokenStore.setPrefix("TOKEN:");
            return redisTokenStore;
        }
    
        /**
         * 初始化密码编码器,指定编码与校验规则,用MD5加密密码
         *
         * @return
         */
        @Bean
        public PasswordEncoder passwordEncoder() {
            // Security官方推荐的BCryptPasswordEncoder加密与校验类
            // 密钥的迭代次数(默认为10)
            //return new BCryptPasswordEncoder(10);
    
            return new PasswordEncoder() {
                /**
                 * 加密
                 * @param rawPassword 原始密码
                 * @return
                 */
                @Override
                public String encode(CharSequence rawPassword) {
                    return DigestUtil.md5Hex(rawPassword.toString());
                }
    
                /**
                 * 校验密码
                 * @param rawPassword       原始密码
                 * @param encodedPassword   加密密码
                 * @return
                 */
                @Override
                public boolean matches(CharSequence rawPassword, String encodedPassword) {
                    return DigestUtil.md5Hex(rawPassword.toString()).equals(encodedPassword);
                }
            };
        }
    
        /**
         * 初始化认证管理对象
         *
         * @return
         * @throws Exception
         */
        @Bean
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
    
        /**
         * 放行和认证规则
         *
         * @param http
         * @throws Exception
         */
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            // 禁用csrf
            http.csrf().disable()
                    .authorizeRequests()
                    // 放行的请求
                    .antMatchers("/oauth/**", "/actuator/**").permitAll()
                    .and()
                    .authorizeRequests()
                    // 其他请求必须认证才能访问
                    .anyRequest().authenticated();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104

    登录认证配置

    创建UserService类实现UserDetailsService类重写loadUserByUsername方法,该方法主要实现登录、认证校验逻辑,这里简单模拟。

    public interface UserMapper {
    
        /**
         * 根据用户名 or 手机号 or 邮箱查询用户信息
         * @param account
         * @return
         */
        @Select("select id, username, phone, email, password, roles from user where " +
                "(username = #{account} or phone = #{account} or email = #{account})")
        Diners selectByAccountInfo(@Param("account") String account);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    @Service
    public class UserService implements UserDetailsService {
    
        @Resource
        private UserMapper userMapper;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            if (StrUtil.hasBlank(username)) {
                throw new RuntimeException("用户名不可为空");
            }
            User user= userMapper.selectByAccountInfo(username);
            if (user == null) {
                throw new UsernameNotFoundException("用户名或密码错误,请重新输入");
            }
            return new User(username, user.getPassword(), AuthorityUtils.commaSeparatedStringToAuthorityList(diners.getRoles()));
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    Oauth2参数配置类

    读取application.yaml文件中的Oauth2配置信息,并封装到ClientOAuth2DataConfiguration类

    package com.example.demo.config;
    import lombok.Data;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.stereotype.Component;
    
    /**
     * 客户端配置类
     */
    @Component
    @ConfigurationProperties(prefix = "client.oauth2")
    @Data
    public class ClientOAuth2DataConfiguration {
    
        /**
         * 客户端标识ID
         */
        private String clientId;
    
        /**
         * 客户端安全码
         */
        private String secret;
    
        /**
         * 授权类型
         */
        private String[] grantTypes;
    
        /**
         * token有效期
         */
        private int tokenValidityTime;
    
        /**
         * refresh-token有效期
         */
        private int refreshTokenValidityTime;
    
        /**
         * 客户端访问范围
         */
        private String[] scopes;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    授权服务配置

    package com.example.demo.config;
    
    import com.example.demo.service.UserService;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.authentication.AuthenticationManager;
    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.token.store.redis.RedisTokenStore;
    import javax.annotation.Resource;
    
    /**
     * 授权服务配置
     */
    @Configuration
    @EnableAuthorizationServer
    public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
    
        /**
         * RedisTokenSore
         */
        @Resource
        private RedisTokenStore redisTokenStore;
    
        /**
         * 认证管理对象
         */
        @Resource
        private AuthenticationManager authenticationManager;
    
        /**
         * 密码编码器
         */
        @Resource
        private PasswordEncoder passwordEncoder;
    
        /**
         * 客户端配置类
         */
        @Resource
        private ClientOAuth2DataConfiguration clientOAuth2DataConfiguration;
    
        /**
         * 登录校验
         */
        @Resource
        private UserService userService;
    
    
        /**
         * 配置令牌端点安全约束
         *
         * @param security
         * @throws Exception
         */
        @Override
        public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
            // 允许访问token的公钥,默认/oauth/token_key是受保护的
            security.tokenKeyAccess("permitAll()")
                    // 允许检查token的状态,默认/oauth/check_token是受保护的
                    .checkTokenAccess("permitAll()");
        }
    
        /**
         * 客户端配置 - 授权模型
         *
         * @param clients
         * @throws Exception
         */
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            clients.inMemory().withClient(clientOAuth2DataConfiguration.getClientId()) // 客户端标识 ID
                    .secret(passwordEncoder.encode(clientOAuth2DataConfiguration.getSecret())) // 客户端安全码
                    .authorizedGrantTypes(clientOAuth2DataConfiguration.getGrantTypes()) // 授权类型
                    .accessTokenValiditySeconds(clientOAuth2DataConfiguration.getTokenValidityTime()) // token 有效期
                    .refreshTokenValiditySeconds(clientOAuth2DataConfiguration.getRefreshTokenValidityTime()) // 刷新 token 的有效期
                    .scopes(clientOAuth2DataConfiguration.getScopes()); // 客户端访问范围
        }
    
        /**
         * 配置授权以及令牌的访问端点和令牌服务
         *
         * @param endpoints
         * @throws Exception
         */
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            // 认证器
            endpoints.authenticationManager(authenticationManager)
                    // 具体登录的方法
                    .userDetailsService(userService)
                    // token 存储的方式:Redis
                    .tokenStore(redisTokenStore);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99

    执行测试

    请求 localhost:8888/oauth/token

    参数设置
    在这里插入图片描述
    在这里插入图片描述
    执行请求
    在这里插入图片描述
    查看Redis
    在这里插入图片描述

    增强令牌

    增强令牌就是丰富、自定义令牌包含的信息,这部分信息是客户端能直接看到的

    重构端点

    重构/oauth/token端点

    /**
     * Oauth2控制器
     */
    @RestController
    @RequestMapping("oauth")
    public class OAuthController {
    
        @Resource
        private TokenEndpoint tokenEndpoint;
    
        @Resource
        private HttpServletRequest request;
    
        /**
         * 自定义Token返回对象
         *
         * @param principal
         * @param parameters
         * @return
         * @throws HttpRequestMethodNotSupportedException
         */
        @PostMapping("token")
        public HashMap<String, Object> postAccessToken(Principal principal, @RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
            OAuth2AccessToken auth2AccessToken = tokenEndpoint.postAccessToken(principal, parameters).getBody();
            DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) auth2AccessToken;
            Map<String, Object> data = new LinkedHashMap(token.getAdditionalInformation());
            data.put("accessToken", token.getValue());
            data.put("expireIn", token.getExpiresIn());
            data.put("scopes", token.getScope());
            if (token.getRefreshToken() != null) {
                data.put("refreshToken", token.getRefreshToken().getValue());
            }
            data.put("path", request.getServletPath());
            return BaseUtil.back(1, data);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    执行测试
    在这里插入图片描述

    重构令牌

    创建SignInIdentity登录认证对象类实现UserDetails

    package com.example.demo.model;
    
    import cn.hutool.core.util.StrUtil;
    import lombok.Getter;
    import lombok.Setter;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.authority.AuthorityUtils;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    import org.springframework.security.core.userdetails.UserDetails;
    
    import java.util.Collection;
    import java.util.List;
    import java.util.stream.Collectors;
    import java.util.stream.Stream;
    
    /**
     * 登录认证对象
     */
    @Getter
    @Setter
    public class SignInIdentity implements UserDetails {
    
        /**
         * 主键
         */
        private Integer id;
        /**
         * 用户名
         */
        private String username;
        /**
         * 昵称
         */
        private String nickname;
        /**
         * 密码
         */
        private String password;
        /**
         * 手机号
         */
        private String phone;
        /**
         * 邮箱
         */
        private String email;
        /**
         * 头像
         */
        private String avatarUrl;
        /**
         * 角色
         */
        private String roles;
        /**
         * 是否有效 0=无效 1=有效
         */
        private int isValid;
        /**
         * 角色集合, 不能为空
         */
        private List<GrantedAuthority> authorities;
    
        /**
         * 获取角色信息
         *
         * @return
         */
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            if (StrUtil.isNotBlank(this.roles)) {
                String[] strings = this.roles.split(",");
                // 获取数据库中的角色信息
                this.authorities = Stream.of(strings).map(role -> {
                    return new SimpleGrantedAuthority(role);
                }).collect(Collectors.toList());
            } else {
                // 如果角色为空则设置为ROLE_USER
                this.authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER");
            }
            return this.authorities;
        }
    
        @Override
        public String getPassword() {
            return this.password;
        }
    
        @Override
        public boolean isAccountNonExpired() {
            return true;
        }
    
        @Override
        public boolean isAccountNonLocked() {
            return true;
        }
    
        @Override
        public boolean isCredentialsNonExpired() {
            return true;
        }
    
        @Override
        public boolean isEnabled() {
            return this.isValid != 0;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109

    修改登录认证

        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            if (StrUtil.hasBlank(username)) {
                throw new RuntimeException("用户名不可为空");
            }
            Diners diners = dinersMapper.selectByAccountInfo(username);
            if (diners == null) {
                throw new UsernameNotFoundException("用户名或密码错误,请重新输入");
            }
            // 初始化登录认证对象
            SignInIdentity signInIdentity = new SignInIdentity();
            // 拷贝属性
            BeanUtils.copyProperties(diners, signInIdentity);
            return signInIdentity;
            // return new User(username, diners.getPassword(), AuthorityUtils.commaSeparatedStringToAuthorityList(diners.getRoles()));
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    令牌增强

        /**
         * 配置授权以及令牌的访问端点和令牌服务
         *
         * @param endpoints
         * @throws Exception
         */
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            // 认证器
            endpoints.authenticationManager(authenticationManager)
                    // 具体登录的方法
                    .userDetailsService(userService)
                    // token 存储的方式:Redis
                    .tokenStore(redisTokenStore)
                    // 令牌增强对象,增强返回的结果
                    .tokenEnhancer((accessToken, authentication) -> {
                        // 获取登录用户的信息,然后设置
                        SignInIdentity signInIdentity = (SignInIdentity) authentication.getPrincipal();
                        LinkedHashMap<String, Object> map = new LinkedHashMap<>();
                        map.put("nickname", signInIdentity.getNickname());
                        map.put("avatarUrl", signInIdentity.getAvatarUrl());
                        DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) accessToken;
                        token.setAdditionalInformation(map);
                        return token;
                    });
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    执行测试

    请求 localhost:8888/oauth/token
    在这里插入图片描述

    资源服务中心

    登录成功,得到token,通过token获取资源

    认证异常配置

    创建MyAuthenticationEntryPoint类,处理认证失败出现异常时的处理逻辑。

    package com.example.demo.config;
    
    import cn.hutool.core.util.StrUtil;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.example.demo.utils.BaseUtil;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.web.AuthenticationEntryPoint;
    import org.springframework.stereotype.Component;
    
    import javax.annotation.Resource;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.PrintWriter;
    import java.util.HashMap;
    
    /**
     * 认证失败处理
     */
    @Component
    public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
    
        @Resource
        private ObjectMapper objectMapper;
    
        /**
         * 认证失败处理逻辑
         *
         * @param request
         * @param response
         * @param authException
         * @throws IOException
         */
        @Override
        public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
            // 返回 JSON
            response.setContentType("application/json;charset=utf-8");
            // 状态码 401
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            // 写出
            PrintWriter out = response.getWriter();
            String errorMessage = authException.getMessage();
            if (StrUtil.isBlank(errorMessage)) {
                errorMessage = "登录失效!";
            }
            HashMap<String, Object> result = BaseUtil.back(0, errorMessage, errorMessage);
            // ResultInfo result = ResultInfoUtil.buildError(ApiConstant.ERROR_CODE, errorMessage, request.getRequestURI());
            out.write(objectMapper.writeValueAsString(result));
            out.flush();
            out.close();
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    创建资源服务

    package com.example.demo.config;
    
    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 javax.annotation.Resource;
    
    /**
     * 资源服务
     */
    @Configuration
    @EnableResourceServer
    public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    
        @Resource
        private MyAuthenticationEntryPoint authenticationEntryPoint;
    
        /**
         * 配置放行的资源
         *
         * @param http
         * @throws Exception
         */
        @Override
        public void configure(HttpSecurity http) throws Exception {
            //所有请求必须认证通过
            http.authorizeRequests()
                    //其他地址需要认证授权;
                    .anyRequest()
                    .authenticated()
                    .and()
                    //下边的路径放行
                    .requestMatchers()
                    .antMatchers("/user/**");
        }
    
        @Override
        public void configure(ResourceServerSecurityConfigurer resources) {
            resources.authenticationEntryPoint(authenticationEntryPoint);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    提供资源

    package com.example.demo.controller;
    
    import com.example.demo.model.SignInIdentity;
    import com.example.demo.utils.BaseUtil;
    import io.micrometer.core.instrument.util.StringUtils;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.oauth2.common.OAuth2AccessToken;
    import org.springframework.security.oauth2.common.OAuth2RefreshToken;
    import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.annotation.Resource;
    import javax.servlet.http.HttpServletRequest;
    import java.util.HashMap;
    
    /**
     * 用户中心
     */
    @RestController
    public class UserController {
    
        @Resource
        private RedisTokenStore redisTokenStore;
    
        /**
         * 获取登录用户的信息
         *
         * @param authentication
         * @return
         */
        @GetMapping("user/getLoginUser")
        public HashMap<String, Object> getCurrentUser(Authentication authentication) {
            SignInIdentity signInIdentity = (SignInIdentity) authentication.getPrincipal();
            HashMap<String, Object> map = new HashMap<>();
            map.put("username", signInIdentity.getUsername());
            map.put("phone", signInIdentity.getPhone());
            map.put("email", signInIdentity.getEmail());
            return BaseUtil.back(1, "获取资源成功", map);
        }
    
        /**
         * 安全退出
         *
         * @param access_token
         * @param authorization
         * @return
         */
        @GetMapping("user/logout")
        public HashMap<String, Object> logout(String access_token, String authorization) {
            // 判断access_token是否为空,为空将authorization赋值给access_token
            if (StringUtils.isBlank(access_token)) {
                access_token = authorization;
            }
            // 判断authorization是否为空
            if (StringUtils.isBlank(access_token)) {
                return BaseUtil.back(1, "退出成功");
            }
            // 判断bearer token是否为空
            if (access_token.toLowerCase().contains("bearer ".toLowerCase())) {
                access_token = access_token.toLowerCase().replace("bearer ", "");
            }
            // 清除redis token信息
            OAuth2AccessToken oAuth2AccessToken = redisTokenStore.readAccessToken(access_token);
            if (oAuth2AccessToken != null) {
                redisTokenStore.removeAccessToken(oAuth2AccessToken);
                OAuth2RefreshToken refreshToken = oAuth2AccessToken.getRefreshToken();
                redisTokenStore.removeRefreshToken(refreshToken);
            }
            return BaseUtil.back(1, "退出成功");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    执行测试

    请求localhost:8888/oauth/token获取token

    {
    	"code": 1,
    	"message": "Successful.",	
    	"data": {
    		"nickname": "test",
    		"avatarUrl": "/test",
    		"accessToken": "2cf71a49-1f62-4e93-b27c-7cb0b4419ab4",
    		"expireIn": 2588653,
    		"scopes": [
    			"api"
    		],
    		"refreshToken": "154aefe0-a0fa-43d4-91fb-c70b1e2998e4",
    		"path": "/oauth/token"
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    使用token获取服务资源,有两种方式:

    方式一:

    请求localhost:8888/user/getLoginUser?access_token=2cf71a49-1f62-4e93-b27c-7cb0b4419ab4获取资源

    {
    	"msg": "获取资源成功",
    	"code": 1,
    	"data": {
    		"phone": "13666666666",
    		"email": null,
    		"username": "test"
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    方式二:

    请求localhost:8888/user/getLoginUser,使用Bearer auth认证

    在这里插入图片描述
    获取资源

    {
    	"msg": "获取资源成功",
    	"code": 1,
    	"data": {
    		"phone": "13666666666",
    		"email": null,
    		"username": "test"
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    校验token

    请求 localhost:8888/oauth/check_token?token=2cf71a49-1f62-4e93-b27c-7cb0b4419ab4

    校验token成功时:

    {
    	"avatarUrl": "/test",
    	"user_name": "test",
    	"scope": [
    		"api"
    	],
    	"nickname": "test",
    	"active": true,
    	"exp": 1629734432,
    	"authorities": [
    		"ROLE_USER"
    	],
    	"client_id": "appId"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    校验token失败时:

    {
    	"error": "invalid_token",
    	"error_description": "Token was not recognised"
    }
    
    • 1
    • 2
    • 3
    • 4

    安全退出

    1.请求localhost:8888/user/logout?access_token=2cf71a49-1f62-4e93-b27c-7cb0b4419ab4

    2.请求localhost:8888/user/logout,使用Bearer auth认证

    {
    	"msg": "退出成功",
    	"code": 1,
    	"data": null
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    退出后再次请求资源

    {
    	"msg": "Invalid access token: 2cf71a49-1f62-4e93-b27c-7cb0b4419ab4",
    	"code": 0,
    	"data": "Invalid access token: 2cf71a49-1f62-4e93-b27c-7cb0b4419ab4"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    网关校验

    添加依赖

            <!-- spring cloud gateway -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-gateway</artifactId>
            </dependency>
            <!-- eureka client -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    配置application.yml

    server:
      port: 9999
    
    spring:
      application:
        name: gateway-server
      cloud:
        gateway:
          discovery:
            locator:
              enabled: true # 开启配置注册中心进行路由功能
              lower-case-service-id: true # 将服务名称转小写
          routes:
            - id: oauth2-server
              uri: lb://oauth2-server
              predicates:
                - Path=/auth/**
              filters:
                - StripPrefix=1
    
    # 自定义参数
    secure:
      ignore:
        urls: # 配置白名单路径
          - /actuator/**
          - /auth/oauth/**
          - /user/getLoginUser
          - /user/logout
    
    # 配置 Eureka Server 注册中心
    eureka:
      instance:
        prefer-ip-address: true
        instance-id: ${spring.cloud.client.ip-address}:${server.port}
      client:
        service-url:
          defaultZone: http://localhost:8080/eureka/        
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    路径白名单配置类

    /**
     * 网关白名单配置
     */
    @Data
    @Component
    @ConfigurationProperties(prefix = "secure.ignore")
    public class IgnoreUrlsConfig {
    
        private List<String> urls;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    网关过滤器

    package com.example.demo.gateway.filter;
    
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.example.demo.utils.BaseUtil;
    import com.example.demo.config.IgnoreUrlsConfig;
    import org.apache.commons.lang.StringUtils;
    import org.springframework.cloud.gateway.filter.GatewayFilterChain;
    import org.springframework.cloud.gateway.filter.GlobalFilter;
    import org.springframework.core.Ordered;
    import org.springframework.core.io.buffer.DataBuffer;
    import org.springframework.http.HttpHeaders;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.MediaType;
    import org.springframework.http.ResponseEntity;
    import org.springframework.http.server.reactive.ServerHttpResponse;
    import org.springframework.stereotype.Component;
    import org.springframework.util.AntPathMatcher;
    import org.springframework.web.client.RestTemplate;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Mono;
    
    import javax.annotation.Resource;
    import java.nio.charset.StandardCharsets;
    import java.util.HashMap;
    
    /**
     * 网关全局过滤器
     */
    @Component
    public class AuthGlobalFilter implements GlobalFilter, Ordered {
    
        @Resource
        private IgnoreUrlsConfig ignoreUrlsConfig;
        @Resource
        private RestTemplate restTemplate;
    
    
        /**
         * 身份校验处理
         *
         * @param exchange
         * @param chain
         * @return
         */
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            // 判断当前的请求是否在白名单中
            AntPathMatcher pathMatcher = new AntPathMatcher();
            boolean flag = false;
            String path = exchange.getRequest().getURI().getPath();
            for (String url : ignoreUrlsConfig.getUrls()) {
                if (pathMatcher.match(url, path)) {
                    flag = true;
                    break;
                }
            }
            // 白名单放行
            if (flag) {
                return chain.filter(exchange);
            }
            // 获取 access_token
            String access_token = exchange.getRequest().getQueryParams().getFirst("access_token");
            // 判断access_token是否为空
            if (StringUtils.isBlank(access_token)) {
                return this.writeError(exchange, "请登录");
            }
            // 校验token是否有效
            String checkTokenUrl = "http://oauth2-server/oauth/check_token?token=".concat(access_token);
            try {
                // 发送远程请求,验证 token
                ResponseEntity<String> entity = restTemplate.getForEntity(checkTokenUrl, String.class);
                // token无效业务逻辑处理
                if (entity.getStatusCode() != HttpStatus.OK) {
                    return this.writeError(exchange, "请求失败");
                }
                if (StringUtils.isBlank(entity.getBody())) {
                    return this.writeError(exchange, "获取token失败");
                }
            } catch (Exception e) {
                return this.writeError(exchange, "token校验失败");
            }
            // 放行
            return chain.filter(exchange);
        }
    
        /**
         * 网关过滤器的排序,数字越小优先级越高
         *
         * @return
         */
        @Override
        public int getOrder() {
            return 0;
        }
    
        @Resource
        private ObjectMapper objectMapper;
    
        public Mono<Void> writeError(ServerWebExchange exchange, String error) {
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.OK);
            response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
            HashMap<String, Object> back = BaseUtil.back(0, error);
            String resultInfoJson;
            DataBuffer buffer = null;
            try {
                resultInfoJson = objectMapper.writeValueAsString(back);
                buffer = response.bufferFactory().wrap(resultInfoJson.getBytes(StandardCharsets.UTF_8));
            } catch (JsonProcessingException ex) {
                ex.printStackTrace();
            }
            return response.writeWith(Mono.just(buffer));
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116

    执行测试

    请求localhost:9999/auth/oauth/token获取token

    {
    	"code": 1,
    	"message": "Successful.",	
    	"data": {
    		"nickname": "test",
    		"avatarUrl": "/test",
    		"accessToken": "7a3d7102-39eb-4d02-be4c-9a705c9db616",
    		"expireIn": 2591999,
    		"scopes": [
    			"api"
    		],
    		"refreshToken": "6c974314-a13c-473a-8cdc-fb8373b3cce5",
    		"path": "/oauth/token"
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    请求localhost:9999/auth/user/getLoginUser?access_token=7a3d7102-39eb-4d02-be4c-9a705c9db616获取服务资源

    {
    	"msg": "获取资源成功",
    	"code": 1,
    	"data": {
    		"phone": "13666666666",
    		"email": null,
    		"username": "test"
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    请求localhost:9999/auth/user/logout?access_token=7a3d7102-39eb-4d02-be4c-9a705c9db616安全退出

    {
    	"msg": "退出成功",
    	"code": 1,
    	"data": null
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
  • 相关阅读:
    pytorch yolov5的输入图像尺寸为指定尺寸
    Vue移动端动态表单生成组件
    charles劫持修改js文件
    对于这个系统的受力分析的疑问
    ArGIS Engine专题(12)之GP模型集成Python脚本实现影像切片服务自动发布
    TC8:SOMEIPSRV_SD_BEHAVIOR_01-04
    基于SSM的Web网页聊天室系统
    C语言实现的多项式合并运算系统
    【小月电子】安路国产FPGA开发板系统学习教程-LESSON6按键消抖
    WPF中动画
  • 原文地址:https://blog.csdn.net/qq_38628046/article/details/119046468