首页 > 其他分享 >Spring Cloud Security OAuth2.0学习

Spring Cloud Security OAuth2.0学习

时间:2022-09-05 13:12:54浏览次数:107  
标签:令牌 Spring token tokenServices 服务器 授权 Security OAuth2.0 public

一、OAuth2授权机制

1.1 为什么需要OAuth2授权

参考OAuth 2.0 的一个简单解释 - 阮一峰的网络日志 (ruanyifeng.com)

1.2 OAuth2四种授权方式

参考OAuth 2.0 的四种方式 - 阮一峰的网络日志 (ruanyifeng.com)

  • 授权码
  • 隐藏式:适用于无后端服务器的第三方应用(纯前端页面)
  • 密码式
  • 凭证式:适用于没有前端的命令行应用

1.3 授权码模式的授权流程

image-20220905083728077

  1. 用户访问和登录第三方网站时,点击微信、QQ、新浪等第三方登录按钮,发送https://b.com/oauth/authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=CALLBACK_URL&scope=read请求到授权服务器;
  2. 授权服务器返回用户登录页面,用户登录后跳转到授权确认页面,用户同意授权后重定向到https://a.com/callback?code=AUTHORIZATION_CODE到前端地址栏中,这个重定向get请求会访问第三方应用的后端服务器;
  3. 第三方应用的后端服务器携带授权码、客户端id、客户端秘钥去授权服务器获取访问令牌;
  4. 授权服务器返回访问令牌到第三方应用的后端服务器
  5. 后端服务器用访问令牌去访问资源服务器获取资源,后端服器把获取到的资源返回给前端

二、Spring Cloud Security OAuth2

Spring Cloud Security OAuth2是一个基于Spring Security实现了OAuth2授权机制的安全框架,框架主要涉及以下四个对象:

  • 授权服务器
  • 资源服务器
  • 客户端:指第三方应用
  • 资源所有者

在Spring Cloud Security包含securityOAuth2两个依赖,应用导入OAuth2会自动导入security依赖

2.1 授权服务器配置

2.1.1 配置依赖

<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>

2.1.2 配置资源所有者认证

为第三方应用授权之前,资源所有者用户必须先登录授权服务器,所以要配置用户登录

配置用户登录的方法,同传统Spring Security一样:

@Configuration
public class BaseSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("admin")
                .password(new BCryptPasswordEncoder().encode("123456"))
                .roles("admin")
                .and()
                .withUser("wexia")
                .password(new BCryptPasswordEncoder().encode("123456"))
                .roles("user");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable().formLogin();
    }

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

用户信息储存在内存中,也可以实现UserDetailService服务从数据库中获取用户信息

2.1.3 配置授权服务器

继承AuthorizationServerConfigurerAdapter配置客户端信息、授权码储存方式、令牌生成及储存方式以及令牌端点的安全约束:

@Configuration
@EnableAuthorizationServer
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private ClientDetailsService clientDetailsService;

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.checkTokenAccess("permitAll()")
                .allowFormAuthenticationForClients();
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("weixia") //客户端ID
            	// 客户端密钥,储存时要加密储存
            	// 获取令牌或远程校验令牌时,客户端传入未加密的客户端密钥,系统会自动进行加密
                .secret(new BCryptPasswordEncoder().encode("123"))
            	// 可选,设置能访问的资源ID
                .resourceIds("res-1")
            	// 授权类型
                .authorizedGrantTypes("authorization_code", "refresh_token")
            	// 授权范围
                .scopes("all")
            	// 重定向地址
                .redirectUris("http://localhost:8082/index.html");

    }

    /**
     *
     * 1、配置授权码储存方式
     * 2. 配置令牌(储存方式、令牌刷新)
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authorizationCodeServices(authorizationCodeServices())
                .tokenServices(authorizationServerTokenServices())
                .pathMapping("/oauth/confirm_access", "/oauth/custom_confirm_access");
    }

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

    @Bean
    public AuthorizationServerTokenServices authorizationServerTokenServices() {
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(tokenStore());
        tokenServices.setSupportRefreshToken(true);
        tokenServices.setClientDetailsService(clientDetailsService);
        tokenServices.setAccessTokenValiditySeconds(60 * 60 * 2);
        tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 3);
        return tokenServices;
    }

    @Bean
    public TokenStore tokenStore() {
        return new InMemoryTokenStore();
    }
}
  • @EnableAuthorizationServer注解导入授权服务器的自动化配置
  • AuthorizationServerSecurityConfigurer配置令牌端点的安全约束,通俗的讲,就是配置访问oauth/token等受限接口的行为(是否允许访问)。checkTokenAccess方法配置是否允许资源服务器访问oauth/token接口进行token鉴权;allowFormAuthenticationForClients方法用于获取令牌时,允许客户端端信息通过请求体传递,如果不没配置只能通过basic认证传递客户端信息(allowFormAuthenticationForClients作用_树欲静而风不止的博客-CSDN博客
  • ClientDetailsServiceConfigurer配置客户端信息,描述了客户端消息如何储存以及客户端ID、客户端密钥、客户端支持的授权类型、授权范围、重定向地址;resourceIds是可选方法,如果配置了资源ID那么客户端只能访问对应ID的资源服务器(资源服务器配置时要设置资源ID),如果不配置资源ID则可以访问任意资源服务器。第三方应用必须在授权服务器先进行备案,才能获取令牌
  • AuthorizationServerEndpointsConfigurer配置授权码、访问令牌及请求转发。authorizationCodeServices方法配置授权码储存方式,框架内置内存和数据库两种储存方式,但授权码有效期短且用过就会失效,一般用内存储存方式就行;tokenServices配置令牌服务,令牌服务提供了令牌存储方式、令牌有效期、是否刷新等配置;pathMapping方法主要用于请求映射,请求A链接会直接重定向到B链接,借此可以实现自定义授权页面、错误页面

2.1.4 用户认证是怎么实现重定向的

授权服务器分别继承了AuthorizationServerConfigurerAdapterWebSecurityConfigurerAdapter,相当于创建了2条filterChain,如下图所示:

image-20220903184839427

AuthorizationServerConfigurerAdapter创建的过滤器只拦截/oauth/token等接口,过滤链中不包含用户认证过滤器,所以要继承WebSecurityConfigurerAdapter来配置资源所有者认证

WebSecurityConfigurerAdapter创建的调用链拦截/**任意链接,提供了表单认证过滤器,但过滤器链不包含鉴权过滤器FilterSecurityInterceptor

  1. 当用户发送http://localhost:8080/oauth/authorize?client_id=weixia&response_type=code&scope=all&redirect_uri=http://localhost:8082/index.html,会进入WebSecurityConfigurerAdapter创建的过滤器链,由于用户没有认证,系统会返回一个/login登录页面;
  2. 当用户成功登录后,会执行UsernamePasswordAuthenticationFilter的successfulAuthentication方法,该方法会重定向链接到用户原先要访问的地址
  3. 步骤2重定向发送的是一个get请求,请求http://localhost:8080/oauth/authorize?client_id=weixia&response_type=code&scope=all&redirect_uri=http://localhost:8082/index.html,内置的控制器AuthorizationEndpoint.authorize会重定向地址到/oauth/confirm_access返回一个授权确认页面
  4. 用户确认授权后,会发送post请求到/oauth/authorize,内置控制器校验成功后把生成的授权码附加到重定向地址中,进行重定向

2.2 资源服务器搭建

2.2.1 配置依赖

<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>

2.2.2 配置授权服务器

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.resourceId("res-1").tokenServices(remoteTokenServices());
    }

    @Bean
    public RemoteTokenServices remoteTokenServices() {
        RemoteTokenServices tokenServices = new RemoteTokenServices();
        tokenServices.setCheckTokenEndpointUrl("http://localhost:8080/oauth/check_token");
        tokenServices.setClientId("weixia");
        tokenServices.setClientSecret("123");
        return tokenServices;
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf().disable().authorizeRequests().anyRequest().authenticated();
    }
}
  • @EnableResourceServer注解导入资源服务器自动配置
  • ResourceServerSecurityConfigurer主要配置资源ID以及令牌服务,令牌服务描述了如何鉴权。RemoteTokenServices是远程token鉴权服务,资源服务器接到第三方应用请求后,会传递客户端信息去访问授权服务器的/oauth/check_token接口,鉴定token是否合法
  • HttpSecurity描述了资源服务器上那些接口需要鉴权

2.2.3 资源服务器创建的过滤器链

chain: any request, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@6704df84, org.springframework.security.web.context.SecurityContextPersistenceFilter@3214bad, org.springframework.security.web.header.HeaderWriterFilter@65d9e72a, org.springframework.security.web.authentication.logout.LogoutFilter@2755617b, org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter@6e8f2094, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@f95d64d, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@3ca17943, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@1412682c, org.springframework.security.web.session.SessionManagementFilter@27df5806, org.springframework.security.web.access.ExceptionTranslationFilter@13004dd8, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@4ce267cc]
  1. 资源服务器过滤器链中新增了OAuth2AuthenticationProcessingFilter,负载对请求中携带的token进行校验,如果校验成功会把用户信息封装成Authentication放到security上下文SecurityContextHolder.getContext()中,表示当前请求登录成功;
  2. 此后流程同传统spring security一样,由FilterSecurityInterceptor进行鉴权,如何实现基于角色的控制或动态授权同之前一样

2.3 第三方应用

第三方应用使用postman进行测试:

  1. 用户在浏览器访问http://localhost:8080/oauth/authorize?client_id=weixia&response_type=code&scope=all&redirect_uri=http://localhost:8082/index.html,重定向到登录界面

  2. 用户输入登录帐号、密码,重定向到授权确认页面,用户同意授权后重定义到http://localhost:8082/index.html?code=DGb0oO

  3. postman发送post请求到/oauth/token,获取访问令牌

    image-20220903193408633

  4. 从步骤3获取access_token,向资源服务器发送请求

    image-20220903193528197

    访问资源服务器时,access_token要放到请求头Authorization中,其值格式为Bearer +token

三、客户端信息数据库储存

客户端信息储存及查询主要涉及的接口是ClientDetailsService,其有两个内置实现类:

  • InMemoryClientDetailsService
  • JdbcClientDetailsService

image-20220903212051845

JdbcClientDetailsService的查询及插入语句可以看出表名及表字段

private static final String BASE_FIND_STATEMENT = "select client_id, client_secret, resource_ids, scope, authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, refresh_token_validity, additional_information, autoapprove from oauth_client_details";
 
private static final String DEFAULT_INSERT_STATEMENT = "insert into oauth_client_details (client_secret, resource_ids, scope, authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, refresh_token_validity, additional_information, autoapprove, client_id) values (?,?,?,?,?,?,?,?,?,?,?)";
  1. 创建数据库表如下:

    DROP TABLE IF EXISTS `oauth_client_details`;
    CREATE TABLE `oauth_client_details` (
      `client_id` varchar(48) NOT NULL,
      `resource_ids` varchar(256) DEFAULT NULL,
      `client_secret` varchar(256) DEFAULT NULL,
      `scope` varchar(256) DEFAULT NULL,
      `authorized_grant_types` varchar(256) DEFAULT NULL,
      `web_server_redirect_uri` varchar(256) DEFAULT NULL,
      `authorities` varchar(256) DEFAULT NULL,
      `access_token_validity` int(11) DEFAULT NULL,
      `refresh_token_validity` int(11) DEFAULT NULL,
      `additional_information` varchar(4096) DEFAULT NULL,
      `autoapprove` varchar(256) DEFAULT NULL,
      PRIMARY KEY (`client_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
  2. 授权服务器添加数据源依赖:

    <!-- 如果引入mybatis start可以省略JDBC依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    
  3. 配置数据源

      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/security_demo?timeZoneServer=Asia/Shanghai&useSSL=false
        username: root
        password: password
    
  4. 配置ClientDetailsService实例

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    	clients.withClientDetails(getClientDetailsService());
    
    }
    
    @Bean
    public JdbcClientDetailsService getClientDetailsService() {
    	JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource);
    	jdbcClientDetailsService.setPasswordEncoder(passwordEncoder);
    	return jdbcClientDetailsService;
    }
    

测试前需要进行客户端信息注册,可以在控制器中调用JdbcClientDetailsService.addClientDetails方法传入一个ClientDetails实例进行注册,系统内置一个ClientDetails接口的实现类BaseClientDetails

@Service
public class ClientServiceImpl implements ClientService {
    @Autowired
    private JdbcClientDetailsService jdbcClientDetailsService;

    @Override
    public ResultVO insertClientDetail(BaseClientDetails baseClientDetails) {
        jdbcClientDetailsService.addClientDetails(baseClientDetails);
        return new ResultVO(ResultStatus.OK, "success", baseClientDetails);
    }
}

前端传递参数时,registeredRedirectUris属性的键值名为redirect_uri,不能直接传web_server_redirect_uri(addClientDetails的方法调用链会自动转换redirect_uri为web_server_redirect_uri储存到数据库中)

image-20220904004338034

如果客户端信息储存时设置了令牌有效期,则authorizationServerTokenServices可以省略有效期配置:

tokenServices.setAccessTokenValiditySeconds(60 * 60 * 2);
tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 3);

四、OAuth2使用JWT令牌

4.1 JwtTokenStore实例配置

第三节我们实现了客户端信息储存到数据库中,下面修改access token的储存方式,access token访问令牌的储存主要涉及TokenStore接口,其内置五个实现类:

image-20220904140620607

JwtTokenStore类同JWT令牌生成相关,其依赖TokenEnhancer接口,JwtTokenStore实例配置如下:

// 实例化JwtTokenStore,依赖JwtAccessTokenConverter
// JwtAccessTokenConverter实现了TokenEnhancer接口
@Bean
public JwtTokenStore jwtTokenStore() {
    return new JwtTokenStore(jwtAccessTokenConverter());
}

@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
    JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
    // 设置jwt生成时的签名(即jwt密钥)
    jwtAccessTokenConverter.setSigningKey("weixia");
    return jwtAccessTokenConverter;
}

JwtAccessTokenConverter负责把默认的UUID token转换为jwt格式,同时支持用户信息和jwt相互转换

4.2 为jwt添加附加信息

JWT 默认生成的用户信息主要是用户角色、用户名等,如果我们希望在生成的 JWT 上面添加额外的信息则要自己实现TokenEnhancer接口接口:

@Component
public class CustomAdditionalInformation implements TokenEnhancer {
    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
        Map<String, Object> additionalInformation = oAuth2AccessToken.getAdditionalInformation();
        additionalInformation.put("author", "weixia");
        ((DefaultOAuth2AccessToken) oAuth2AccessToken).setAdditionalInformation(additionalInformation);
        return oAuth2AccessToken;
    }
}

4.3 配置authorizationServerTokenServices,令TokenEnhancer生效

access token默认由DefaultTokenServices.createAccessToken方法生成,该方法最后会检查是否存在token增强器,如果存在则按装传入的增强器顺序依次执行enhance方法

@Bean
public AuthorizationServerTokenServices authorizationServerTokenServices() {
    DefaultTokenServices tokenServices = new DefaultTokenServices();

    tokenServices.setTokenStore(jwtTokenStore());
    TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
    tokenEnhancerChain.setTokenEnhancers(Arrays.asList(jwtAccessTokenConverter(), customAdditionalInformation));
    tokenServices.setTokenEnhancer(tokenEnhancerChain);

    tokenServices.setSupportRefreshToken(true);
    tokenServices.setClientDetailsService(getClientDetailsService());
    //tokenServices.setAccessTokenValiditySeconds(60 * 60 * 2);
    //tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 3);
    return tokenServices;
}

token增强链的顺序要保证JwtAccessTokenConverter位于附加信息增强器前面,生成的jwt如下所示,其中jti则是原始access token

image-20220904145819693

JSON Web Tokens - jwt.io查看JWT负荷部分解析后数据如下:

image-20220904145947077

4.4 资源服务器配置,实现JWT认证

配置JwtTokenStore实例,修改ResourceServerSecurityConfigurer配置,改为用tokenStore鉴权

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.resourceId("res-1").tokenStore(jwtTokenStore());
    }

    @Bean
    public JwtTokenStore jwtTokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
        jwtAccessTokenConverter.setSigningKey("weixia");
        return jwtAccessTokenConverter;
    }
    
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf().disable().authorizeRequests().anyRequest().authenticated();
    }
}

当第三方应用携带JWT令牌访问资源服务器时,过滤器OAuth2AuthenticationProcessingFilter调用认证管理器校验JWT合法性并解析JWT字符串转换为Authentication对象放到security上下文中

五、自定义授权界面

  • 引入Thyemeleaf依赖,强烈建议clean-install-重启IDEA,避免灵异事件

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    
  • 配置Thyemeleaf

    spring:  
      thymeleaf:
        cache: false
    
  • 编写自定义页面,放到类路径下的templates中

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>授权</title>
    </head>
    <!-- 省略样式 -->
    <body style="margin: 0px">
    <div class="title">
        <div class="title-right">OAuth2 自定义授权页面</div>
    </div>
    <div class="container">
        <p>昵称,账号</p>
        授权后表明你已同意
        <form method="post" action="/oauth/authorize">
            <input type="hidden" name="user_oauth_approval" value="true">
            <input type="hidden" name="scope.all" value="true">
            <button class="btn" name="authorize" value="Authorize" type="submit"> 同意/授权</button>
        </form>
    </div>
    </body>
    </html>
    

    表单参数user_oauth_approval、scope.all、authorize必传,请求接口为/oauth/authorize

  • 请求映射配置,把/oauth/confirm_access重定向到自定义接口

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            endpoints.authorizationCodeServices(authorizationCodeServices())
                    .tokenServices(authorizationServerTokenServices())
                    .pathMapping("/oauth/confirm_access", "/oauth/custom_confirm_access");
    }
    
  • 请求接口控制器

    @Controller
    public class ConfirController {
    
        @RequestMapping("/oauth/custom_confirm_access")
        public String getAccessConfirmation(Map<String, Object> model, HttpServletRequest request) throws Exception {
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            model.put("username", authentication.getName());
            return "auth";
        }
    }
    

六、资源服务器认证异常和授权异常

6.1 认证异常(token无效异常)

客户端访问资源服务器时,OAuth2AuthenticationProcessingFilter过滤器检查请求头是否携带Authorization并按照Bearer token格式提取token,如果token值为空则直接执行下个过滤器,不为空则会校验token

如果token校验结果为无效令牌,会抛出OAuth2Exception异常并由authenticationEntryPoint.commence方法处理,该方法把异常信息输出并直接中断过滤器链,我们可以实现AuthenticationEntryPoint接口返回统一异常信息

@Component
public class TokenAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Autowired
    private ObjectMapper objectMapper;
    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        httpServletResponse.setCharacterEncoding("utf-8");
        httpServletResponse.setContentType("application/json");
        ResultVO result = new ResultVO(ResultStatus.NO, "token校验失败", null);
        objectMapper.writeValue(httpServletResponse.getOutputStream(), result);
    }
}

通过配置ResourceServerSecurityConfigurer配置异常处理器:

@Autowired
private TokenAuthenticationEntryPoint tokenAuthenticationEntryPoint;

@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
    resources.resourceId("res-1")
        .tokenStore(jwtTokenStore())
        .authenticationEntryPoint(tokenAuthenticationEntryPoint);
}

private AccessDeniedHandler accessDeniedHandler = new OAuth2AccessDeniedHandler();

ResourceServerSecurityConfigurer还有一个访问拒绝处理器,不知何种情景下会调用,待完善...

注意:

如果配置路径授权忽略,比如antMatchers("/play").permitAll(),请求时如果携带了token而token无效,同样会抛出token认证异常

6.2 授权异常

6.2.1 未认证用户访问受限资源异常

针对访问资源服务器时未携带token,但资源必须登录后才能访问而抛出的异常。该异常处理器同传统

Spring Security配置一样:

@Component
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Autowired
    private ObjectMapper objectMapper;
    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        httpServletResponse.setCharacterEncoding("utf-8");
        httpServletResponse.setContentType("application/json");
        ResultVO result = new ResultVO(ResultStatus.NO, "该资源需要登录访问", null);
        objectMapper.writeValue(httpServletResponse.getOutputStream(), result);
    }
}

6.2.1 已认证用户权限不足异常

@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        httpServletResponse.setCharacterEncoding("utf-8");
        httpServletResponse.setContentType("application/json");
        ResultVO result = new ResultVO(ResultStatus.NO, "权限不足!", null);
        objectMapper.writeValue(httpServletResponse.getOutputStream(), result);
    }
}

6.2.2 配置授权异常处理器

@Autowired
private MyAuthenticationEntryPoint myAuthenticationEntryPoint;
@Autowired
private MyAccessDeniedHandler myAccessDeniedHandler;

@Override
public void configure(HttpSecurity http) throws Exception {
    http.csrf().disable().authorizeRequests()
        .antMatchers("/world").hasRole("user")
        .antMatchers("/play").permitAll()
        .anyRequest().authenticated()
        .and()
        .exceptionHandling()
        .authenticationEntryPoint(myAuthenticationEntryPoint)
        .accessDeniedHandler(myAccessDeniedHandler);
}

@Bean
public ObjectMapper objectMapper() {
    return new ObjectMapper();
}

七、其它事项

7.1 资源服务器全局资源忽略

资源服务器配置一般继承自ResourceServerConfigurerAdapter,ResourceServerConfigurerAdapter暂时未找到设置全局忽略的选项,如果通过ResourceServerConfigurerAdapter中的HttpSecurity配置请求忽略路径,请求依然会走一遍过滤器链。

如果再添加一份配置继承WebSecurityConfigurerAdapter,默认会再添加一个匹配任意路径的filterChain,我们可以让新增加的这个过滤器链匹配一个不用的地址,比如这样:

@Configuration
public class BaseSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/world");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.antMatcher("/XX-XXXXXX/**").authorizeRequests().anyRequest().denyAll()
                .and()
                .csrf().disable();
    }
}

这样既通过WebSecurity配置了全局资源忽略,又避免过滤器链冲突,但上面实现的不够优雅

7.2 授权服务器异常处理

实现太复杂,参考以下链接:

OAuth2.0实战:认证、资源服务异常自定义! - 知乎 (zhihu.com)

SpringSecurityOauth2系列学习(五):授权服务自定义异常处理 - 硝酸铜 - 博客园 (cnblogs.com)

标签:令牌,Spring,token,tokenServices,服务器,授权,Security,OAuth2.0,public
From: https://www.cnblogs.com/weixia-blog/p/16657737.html

相关文章

  • Spring事务
    1.事务的四大特性(ACID)●原子性(Atomicity)共生死,要么全部成功,要么全部失败!●一致性(Consistency)事务在执行前后,数据库中数据要保持一致性状态。(如转账的过程账户......
  • springboot聚合项目搭建
    springboot聚合项目搭建1、简介1.1、什么是聚合项目?一个项目中包含多个子项目的项目。结构:|-父模块---|子模块1---|子模块2---|子模块31.2、聚合项目有什么......
  • 6个基于 Spring Boot 的开源社区项目!功能强大,界面炫酷
    整理了6个不错的基于SpringBoot开发的社区类项目,每个都非常不错!你可以参考这些项目用来作为自己的项目经验,或者你可以基于这些项目搭建一个自己的知识社区。原创不......
  • Spring(五)-Spring的其他知识点
    1、细说ServletContext、WebApplicationContext、Servlet的初始化参考博客-->细说ServletContext、WebApplicationContext、Servlet的初始化......
  • 【转】spring-session-data-redis核心原理
    这个组件的核心本质就是在实现单点登录SSO问题,将用户的登录session信息从原来的存储在jvm中转移到redis中去,微服务架构下每个应用接到请求都不会从自己的节点解析用户登录......
  • springMVC 获取参数
    1.servlet@GetMapping("/params")publicStringtest5(HttpServletRequestrequest){Stringusername=request.getParameter("username");Stringpassword......
  • SpringCloud 使用 Hystrix 实现【客户端】降级
    前面已经介绍了Hystrix服务端降级的实现方案,本篇博客将介绍Hystrix在客户端降级的实现方案。由于我使用最新版的SpringCloud(版本2021.0.3)实现客户端降级没有成功,所......
  • 2.注解开发springmvc
    通用配置:<?xmlversion="1.0"encoding="UTF-8"?><beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-i......
  • Spring的自动装配
    Bean的自动装配自动装配说明自动装配是使用spring满足bean依赖的一种方法spring会在应用上下文中为某个bean寻找其依赖的bean。Spring的自动装配需要从两个角度来实......
  • springboot Invalid bound statement (not found): com.xx.dao.%Dao.login
    解决方法:需要注意一下application.xml配置文件的MyBatis的配置的mapper-locations的路径参考的这篇博客:(133条消息)Invalidboundstatement(notfound):com.exampl......