Spring-Security(二)OAuth2认证详解(持续更新)

Spring Security & Oauth2系列:
 
Spring Security(一) 源码分析及认证流程
Spring Security(二)OAuth2认证详解及自定义异常处理

文章目录

  • 1、OAuth2.0 简介
    • 1.1 OAuth2.0 相关名词解释
    • 1.2 四种授权模式
  • 1.3 、OAuth2框架
    • 1.4 OAuth 2.0客户端提供功能
  • 2、OAuth 2.0 认证服务
    • 2.1 Spring Security OAuth2 提供的程序实现
      • 2.1.1 授权服务
      • 2.1.2 资源服务
    • 2.2 集成 OAuth 2.0 认证授权及资源管理
      • 2.2.1 项目准备
      • 2.2.1 配置授权服务
        • 2.2.1.1 授权服务配置
        • 2.2.1.2 客户端加载策略配置
        • 2.2.1.3 令牌管理策略
        • 2.2.1.4 自定义定义UserService实现UserDetailsService
        • 2.2.1.5 定义令牌端点上的安全约束
      • 2.2.2 添加SpringSecurity配置
      • 2.2.3 Oauth2 验证
    • 2.3、Spring Security oauth2 授权认证核心源码分析
      • 2.3.1 /oauth/token 认证核心处理流程图
      • 2.3.2 TokenEndpoint(/oauth/token) 认证源码分析
    • 2.4 资源服务器
      • 2.4.1 资源服务器配置
      • 2.4.2 使用令牌获取受保护资源
      • 2.4.3 源码分析
        • 2.4.3.1 OAuth2AuthenticationProcessingFilter
        • 2.4.3.2 BearerTokenExtractor
        • 2.4.3.3 OAuth2AuthenticationManager
  • 3、OAuth2 扩展
    • 3.1 自定义异常处理
      • 3.1.1 自定义授权端点处理异常
      • 3.1.2 自定义匿名用户访问无权限资源时的异常
      • 3.1.3 自定义受OAuth2令牌保护的资源认证失败异常
      • 3.1.4 密码认证自定义异常信息
        • 思路
      • 3.1.5 Security自定义异常分析总结
  • 附录

1、OAuth2.0 简介

OAuth 2.0是用于授权的行业标准协议。OAuth 2.0为简化客户端开发提供了特定的授权流,包括Web应用、桌面应用、移动端应用等。

1.1 OAuth2.0 相关名词解释

  • Resource owner(资源拥有者):拥有该资源的最终用户,他有访问资源的账号密码;
  • Resource server(资源服务器):拥有受保护资源的服务器,如果请求包含正确的访问令牌,可以访问资源;
  • Client(客户端):访问资源的客户端,会使用访问令牌去获取资源服务器的资源,可以是浏览器、移动设备或者服务器;
  • Authorization server(认证服务器):用于认证用户的服务器,如果客户端认证通过,发放访问资源服务器的令牌。

1.2 四种授权模式

  • Authorization Code(授权码模式):正宗的OAuth2的授权模式,客户端先将用户导向认证服务器,登录后获取授权码,然后进行授权,最后根据授权码获取访问令牌;
  • Implicit(简化模式):和授权码模式相比,取消了获取授权码的过程,直接获取访问令牌;
  • Resource Owner Password Credentials(密码模式):客户端直接向用户获取用户名和密码,之后向认证服务器获取访问令牌;
  • Client Credentials(客户端模式):客户端直接通过客户端认证(比如client_id和client_secret)从认证服务器获取访问令牌。

1.3 、OAuth2框架

Spring Security提供了OAuth 2.0 完整支持,主要包括:

  • OAuth 2.0核心 - spring-security-oauth2-core.jar:包含为OAuth 2.0授权框架和OpenID Connect Core 1.0提供支持的核心类和接口;
  • OAuth 2.0客户端 - spring-security-oauth2-client.jar:Spring Security对OAuth 2.0授权框架和OpenID Connect Core 1.0的客户端支持;
  • OAuth 2.0 JOSE - spring-security-oauth2-jose.jar:包含Spring Security对JOSE(Javascript对象签名和加密)框架的支持。框架旨在提供安全地传输双方之间的权利要求的方法。它由一系列规范构建:
    JSON Web令牌(JWT)
    JSON Web签名(JWS)
    JSON Web加密(JWE)
    JSON Web密钥(JWK)

要使用OAuth2,需要引入spring-security-oauth2模块,通过之前源码分析,Spring 通过OAuth2ImportSelector类对Oauth2.0进行支持,当引入oauth2模块,Spring会自动启用 OAuth2 客户端配置 OAuth2ClientConfiguration。

1.4 OAuth 2.0客户端提供功能

OAuth 2.0客户端功能为OAuth 2.0授权框架中定义的客户端角色提供支持。
可以使用以下主要功能:

  • 授权代码授予
  • 客户凭证授权
  • Servlet环境的WebClient扩展(用于发出受保护的资源请求)

HttpSecurity.oauth2Client()提供了许多用于自定义OAuth 2.0 Client的配置选项。

@EnableWebSecurity
public class OAuth2ClientSecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.oauth2Client().clientRegistrationRepository(this.clientRegistrationRepository()).authorizedClientRepository(this.authorizedClientRepository()).authorizedClientService(this.authorizedClientService()).authorizationCodeGrant().authorizationRequestRepository(this.authorizationRequestRepository()).authorizationRequestResolver(this.authorizationRequestResolver()).accessTokenResponseClient(this.accessTokenResponseClient());}
}

2、OAuth 2.0 认证服务

Spring Security OAuth2 实现了OAuth 2.0授权服务,简化了程序员对OAuth 2.0的实现,仅需要简单配置OAuth 2.0认证参数即可快速实现认证授权功能。

2.1 Spring Security OAuth2 提供的程序实现

Spring Security OAuth2 中的提供者角色实际上是在授权服务和资源服务之间分配的,使用Spring Security OAuth2,您可以选择将它们拆分到两个应用程序中,并具有多个共享的资源服务授权服务。

2.1.1 授权服务

对令牌的请求由Spring MVC控制器端点处理,对受保护资源的访问由标准Spring Security请求过滤器处理。为了实现OAuth 2.0授权服务器,Spring Security过滤器链中需要以下端点:

  • AuthorizationEndpoint用于服务于授权请求。预设网址:/oauth/authorize
  • TokenEndpoint用于服务访问令牌的请求。预设网址:/oauth/token

2.1.2 资源服务

要实现OAuth 2.0资源服务器,需要以下过滤器:

  • OAuth2AuthenticationProcessingFilter用于加载的身份验证给定令牌的认证访问请求。

2.2 集成 OAuth 2.0 认证授权及资源管理

2.2.1 项目准备

  • 引入依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- ... other dependency elements ... --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-oauth2</artifactId></dependency>

2.2.1 配置授权服务

在配置授权服务器时,必须考虑客户端用于从最终用户获取访问令牌的授予类型(例如,授权代码,用户凭据,刷新令牌)。服务器的配置用于提供客户端详细信息服务和令牌服务的实现,并全局启用或禁用该机制的某些方面。但是请注意,可以为每个客户端专门配置权限,使其能够使用某些授权机制和访问授权。也就是说,仅因为您的提供程序配置为支持“客户端凭据”授予类型,并不意味着授权特定的客户端使用该授予类型。
使用@EnableAuthorizationServer注解开启Oauth2认证。

@EnableAuthorizationServer批注用于配置OAuth 2.0授权服务器机制以及任何@Beans实现的机制AuthorizationServerConfigurer(有一个便捷的适配器实现,其中包含空方法)。以下功能委托给由Spring创建并传递到的单独的配置器AuthorizationServerConfigurer:

  • ClientDetailsServiceConfigurer:定义客户端详细信息服务的配置程序。可以初始化客户详细信息,或者您可以仅引用现有商店。
  • AuthorizationServerSecurityConfigurer:定义令牌端点上的安全约束。
  • AuthorizationServerEndpointsConfigurer:定义授权和令牌端点以及令牌服务。

提供者配置的一个重要方面是将授权代码提供给OAuth客户端的方式(在授权代码授予中)。OAuth客户端通过将最终用户定向到授权页面来获得授权码,用户可以在该页面上输入她的凭据,从而导致从提供者授权服务器重定向回带有授权码的OAuth客户端。

源码清单:

@Configuration
@EnableAuthorizationServer
public class Oauth2ServerConfig extends AuthorizationServerConfigurerAdapter {@Autowiredprivate PasswordEncoder passwordEncoder;@Autowiredprivate AuthenticationManager authenticationManager;@Autowiredprivate UserService userService;/*** 自定义授权服务配置* 使用密码模式需要配置*/@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) {endpoints.authenticationManager(authenticationManager).userDetailsService(userService);}/*** 配置认证客户端* @param clients* @throws Exception*/@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {//自定义客户端配置}/*** 自定义授权令牌端点的安全约束* @param security* @throws Exception*/@Overridepublic void configure(AuthorizationServerSecurityConfigurer security) throws Exception {//自定义安全约束//....}
}
2.2.1.1 授权服务配置

AuthorizationServerEndpointsConfigurer 定义授权和令牌端点以及令牌服务。

endpoints.tokenStore(tokenStore)//自定义令牌存储策略//默认除密码模式外,所有授权模式均支持,密码模式需要显示注入authenticationManager开启.authenticationManager(authenticationManager).userDetailsService(userDetailServiceImpl)//自定义用户密码加载服务.tokenGranter(tokenGranter)//定义控制授权.exceptionTranslator(webResponseExceptionTranslator);//自定义异常解析
2.2.1.2 客户端加载策略配置

ClientDetailsServiceConfigurer(从您的回调AuthorizationServerConfigurer)可以用来定义一个内存中或JDBC实现客户的细节服务。客户的重要属性是:

  • clientId:(必填)客户端ID。
  • secret:(对于受信任的客户端是必需的)客户端密钥(如果有)。
  • scope:客户端的范围受到限制。如果范围未定义或为空(默认值),则客户端不受范围的限制。
  • authorizedGrantTypes:授权客户使用的授权类型。默认值为空。
  • authorities:授予客户端的权限(常规的Spring Security权限)。

可以通过直接访问底层存储(例如的情况下为数据库表JdbcClientDetailsService)或通过ClientDetailsManager接口(这两种实现都ClientDetailsService可以实现)来更新正在运行的应用程序中的客户端详细信息。

  • 内存加载客户端配置,直接通过ClientDetailsServiceConfigurer添加客户端配置
@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {clients.inMemory().withClient("admin")//配置client_id.secret(passwordEncoder.encode("admin123456"))//配置client_secret.accessTokenValiditySeconds(3600)//配置访问token的有效期.refreshTokenValiditySeconds(864000)//配置刷新token的有效期.redirectUris("http://www.baidu.com")//配置redirect_uri,用于授权成功后跳转.scopes("all")//配置申请的权限范围.authorizedGrantTypes("authorization_code","password","client_credentials","refresh_token");//配置grant_type,表示授权类型}
  • 自定义ClientDetailsService,redis+jdbc方式加载客户端缓存
    @Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {clients.withClientDetails(redisClientDetailsService);redisClientDetailsService.loadAllClientToCache();//}@Servicepublic class RedisClientDetailsService extends JdbcClientDetailsService {//继承JdbcClientDetailsService,扩展redis缓存加载客户端,优先从缓存获取客户端配置,缓存没有再从数据库加载
2.2.1.3 令牌管理策略

AuthorizationServerTokenServices定义了管理OAuth 2.0令牌所需的操作。在开发过程需要注意:

  • 创建访问令牌后,必须存储身份验证,以便接受访问令牌的资源以后可以引用它。
  • 访问令牌用于加载用于授权其创建的身份验证。

在创建AuthorizationServerTokenServices实现时,您可能需要考虑使用DefaultTokenServices,可以使用插入许多策略来更改访问令牌的格式和存储。默认情况下,它会通过随机值创建令牌,并处理所有其他事务(除了将令牌委派给的令牌的持久性)TokenStore。默认存储是内存中的实现。

  • InMemoryTokenStore对于单个服务器,默认设置非常合适(例如,低流量,并且在发生故障的情况下不与备份服务器进行热交换)。大多数项目都可以从此处开始,并且可以在开发模式下以这种方式运行,以轻松启动没有依赖性的服务器。

  • JdbcTokenStore是JDBC版本的同样的事情,它存储在关系数据库中令牌数据。如果可以在服务器之间共享数据库,请使用JDBC版本;如果只有一个,则可以扩展同一服务器的实例;如果有多个组件,则可以使用Authorization and Resources Server。要使用,JdbcTokenStore您需要在类路径上使用“ spring-jdbc”。

  • 存储的JSON Web令牌(JWT) 版本将有关授权的所有数据编码到令牌本身中(因此根本没有后端存储,这是一个很大的优势)。一个缺点是您不能轻易地撤销访问令牌,因此通常授予它们的期限很短,并且撤销是在刷新令牌处进行的。 另一个缺点是,如果您在令牌中存储了大量用户凭证信息,则令牌会变得很大。JwtTokenStore是不是一个真正的“存储”在这个意义上,它不坚持任何数据,但它起着翻译令牌值和认证信息相同的角色DefaultTokenServices

2.2.1.4 自定义定义UserService实现UserDetailsService
@Component
public class UserService implements UserDetailsService {@Autowiredprivate QtAdminService qtAdminService;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {String clientId = "admin";UserDto userDto = qtAdminService.loadUserByUsername(username);if (userDto == null) {throw new UsernameNotFoundException(MessageConstant.USERNAME_PASSWORD_ERROR);}userDto.setClientId(clientId);SecurityUser securityUser = new SecurityUser(userDto);// 这里用太多if..else了,可以使用枚举进行优化if (!securityUser.isEnabled()) {throw new DisabledException(MessageConstant.ACCOUNT_DISABLED);} else if (!securityUser.isAccountNonLocked()) {throw new LockedException(MessageConstant.ACCOUNT_LOCKED);} else if (!securityUser.isAccountNonExpired()) {throw new AccountExpiredException(MessageConstant.ACCOUNT_EXPIRED);} else if (!securityUser.isCredentialsNonExpired()) {throw new CredentialsExpiredException(MessageConstant.CREDENTIALS_EXPIRED);}return securityUser;}
}
2.2.1.5 定义令牌端点上的安全约束

在对请求授权的端点进行访问之前需要对授权信息中传递的客户端信息进行认证,客户端认证通过后才会访问授权端点。根据授权参数传递方式不同,对客户端进行认证的Filter也可能不一样:

  • 请求/oauth/token的,如果配置支持allowFormAuthenticationForClients的,且url中有client_id和client_secret的会走ClientCredentialsTokenEndpointFilter
  • 请求/oauth/token的,如果没有支持allowFormAuthenticationForClients或者有支持但是url中没有client_id和client_secret的,走BasicAuthenticationFilter认证

可以AuthorizationServerSecurityConfigurer添加客户端信息验证策略

@Overridepublic void configure(AuthorizationServerSecurityConfigurer security) throws Exception {security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()").addTokenEndpointAuthenticationFilter(customBasicAuthenticationFilter);//添加自定义客户端验证策略}//客户端验证策略控制
public void configure(HttpSecurity http) throws Exception {this.frameworkEndpointHandlerMapping();if (this.allowFormAuthenticationForClients) {this.clientCredentialsTokenEndpointFilter(http);}Iterator var2 = this.tokenEndpointAuthenticationFilters.iterator();while(var2.hasNext()) {Filter filter = (Filter)var2.next();http.addFilterBefore(filter, BasicAuthenticationFilter.class);}http.exceptionHandling().accessDeniedHandler(this.accessDeniedHandler);}private ClientCredentialsTokenEndpointFilter clientCredentialsTokenEndpointFilter(HttpSecurity http) {ClientCredentialsTokenEndpointFilter clientCredentialsTokenEndpointFilter = new ClientCredentialsTokenEndpointFilter(this.frameworkEndpointHandlerMapping().getServletPath("/oauth/token"));clientCredentialsTokenEndpointFilter.setAuthenticationManager((AuthenticationManager)http.getSharedObject(AuthenticationManager.class));OAuth2AuthenticationEntryPoint authenticationEntryPoint = new OAuth2AuthenticationEntryPoint();authenticationEntryPoint.setTypeName("Form");authenticationEntryPoint.setRealmName(this.realm);clientCredentialsTokenEndpointFilter.setAuthenticationEntryPoint(authenticationEntryPoint);clientCredentialsTokenEndpointFilter = (ClientCredentialsTokenEndpointFilter)this.postProcess(clientCredentialsTokenEndpointFilter);http.addFilterBefore(clientCredentialsTokenEndpointFilter, BasicAuthenticationFilter.class);return clientCredentialsTokenEndpointFilter;}private ClientDetailsService clientDetailsService() {return (ClientDetailsService)((HttpSecurity)this.getBuilder()).getSharedObject(ClientDetailsService.class);}private FrameworkEndpointHandlerMapping frameworkEndpointHandlerMapping() {return (FrameworkEndpointHandlerMapping)((HttpSecurity)this.getBuilder()).getSharedObject(FrameworkEndpointHandlerMapping.class);}public void addTokenEndpointAuthenticationFilter(Filter filter) {this.tokenEndpointAuthenticationFilters.add(filter);}

2.2.2 添加SpringSecurity配置

允许认证相关路径的访问及表单登录

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}@Overridepublic void configure(HttpSecurity http) throws Exception {http.csrf().disable().authorizeRequests().antMatchers("/oauth/**", "/login/**", "/logout/**").permitAll().anyRequest().authenticated().and().formLogin().permitAll();}
}

2.2.3 Oauth2 验证

启动应用,进行Oauth2 认证服务进行验证
Oauth2 密码模式验证

  • 使用密码请求该地址获取访问令牌:http://localhost:10001/oauth/token
  • 使用Basic认证通过client_id和client_secret构造一个Authorization头信息;
    认证客户端头信息
  • 在body中添加以下参数信息,通过POST请求获取访问令牌;
    密码模式获取令牌
{"access_token": "a690d4e6-185f-4d1d-bc62-0067bd8b6ec9","token_type": "bearer","refresh_token": "55a04005-e2d9-44df-99df-01b57429d424","expires_in": 3599,"scope": "all"
}

2.3、Spring Security oauth2 授权认证核心源码分析

OAuth2 授权认证大致可以分为两步:

  • 客户端认证Filter拦截/oauth/token请求,对授权参数传递的client_id和client_secret进行认证,认证通过继续访问/oauth/token端点;
  • /oauth/token端点进行授权认证。

2.3.1 /oauth/token 认证核心处理流程图

2.3.2 TokenEndpoint(/oauth/token) 认证源码分析

@RequestMapping(value = {"/oauth/token"},method = {RequestMethod.POST})public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {if (!(principal instanceof Authentication)) {throw new InsufficientAuthenticationException("There is no client authentication. Try adding an appropriate authentication filter.");} else {//1. 获取clientIdString clientId = this.getClientId(principal);//2. 根据客户端id加载客户端信息ClientDetails authenticatedClient = this.getClientDetailsService().loadClientByClientId(clientId);//3. 根据客户端信息和请求参数组装TokenRequestTokenRequest tokenRequest = this.getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);//4. 有没有传clientId验证if (clientId != null && !clientId.equals("") && !clientId.equals(tokenRequest.getClientId())) {throw new InvalidClientException("Given client ID does not match authenticated client");} else {if (authenticatedClient != null) {//5. 授权范围scope校验this.oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient);}//6. grant_type是否存在值,对应四种授权模式和刷新tokenif (!StringUtils.hasText(tokenRequest.getGrantType())) {throw new InvalidRequestException("Missing grant type");//是否简化模式} else if (tokenRequest.getGrantType().equals("implicit")) {throw new InvalidGrantException("Implicit grant type not supported from token endpoint");} else {//是否是授权码模式if (this.isAuthCodeRequest(parameters) && !tokenRequest.getScope().isEmpty()) {this.logger.debug("Clearing scope of incoming token request");tokenRequest.setScope(Collections.emptySet());}//是否刷新令牌if (this.isRefreshTokenRequest(parameters)) {tokenRequest.setScope(OAuth2Utils.parseParameterList((String)parameters.get("scope")));}//7. 授权控制,并返回AccessTokenOAuth2AccessToken token = this.getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);if (token == null) {throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());} else {return this.getResponse(token);}}}}}

2.4 资源服务器

2.4.1 资源服务器配置

资源服务器(可以与授权服务器或单独的应用程序相同)提供受OAuth2令牌保护的资源。Spring OAuth提供了实现此保护的Spring Security身份验证过滤器。您可以@EnableResourceServer在@Configuration类上将其打开,并使用进行配置(根据需要)ResourceServerConfigurer。可以配置以下功能:

  • tokenServices:定义令牌服务(的实例ResourceServerTokenServices)的bean 。
  • resourceId:资源的ID(可选,但建议使用,并且将由auth服务器验证(如果存在))。
  • 资源服务器的其他扩展点(例如,tokenExtractor用于从传入请求中提取令牌)
  • 请求受保护资源的匹配器(默认为全部)
  • 受保护资源的访问规则(默认为普通的“已认证”)
  • HttpSecuritySpring Security中配置程序允许的受保护资源的其他自定义

@EnableResourceServer注释添加类型的过滤器OAuth2AuthenticationProcessingFilter 自动Spring Security的过滤器链。
代码清单:

@Configuration
@EnableResourceServer
public class Oauth2SourceConfig {//配置资源url保护策略@Overridepublic void configure(HttpSecurity http) throws Exception {http.authorizeRequests().anyRequest().authenticated().and().requestMatchers().antMatchers("/user/**");//配置需要保护的资源路径}//自定义资源保护令牌策略public void configure(ResourceServerSecurityConfigurer resources) throws Exception {resources.tokenStore(tokenStore);}
}

2.4.2 使用令牌获取受保护资源

2.4.3 源码分析

2.4.3.1 OAuth2AuthenticationProcessingFilter

资源服务认证入口Filter

public class OAuth2AuthenticationProcessingFilter implements Filter, InitializingBean {//省略......public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {boolean debug = logger.isDebugEnabled();HttpServletRequest request = (HttpServletRequest)req;HttpServletResponse response = (HttpServletResponse)res;try {//1. 从BearerTokenExtractor 获取Authentication 信息Authentication authentication = this.tokenExtractor.extract(request);if (authentication == null) {if (this.stateless && this.isAuthenticated()) {if (debug) {logger.debug("Clearing security context.");}SecurityContextHolder.clearContext();}if (debug) {logger.debug("No token in request, will continue chain.");}} else {request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, authentication.getPrincipal());if (authentication instanceof AbstractAuthenticationToken) {AbstractAuthenticationToken needsDetails = (AbstractAuthenticationToken)authentication;needsDetails.setDetails(this.authenticationDetailsSource.buildDetails(request));}//2. OAuth2AuthenticationManager 进行token认证Authentication authResult = this.authenticationManager.authenticate(authentication);if (debug) {logger.debug("Authentication success: " + authResult);}//3. 将认证结果放置SecurityContextHolder上下文this.eventPublisher.publishAuthenticationSuccess(authResult);SecurityContextHolder.getContext().setAuthentication(authResult);}} catch (OAuth2Exception var9) {SecurityContextHolder.clearContext();if (debug) {logger.debug("Authentication request failed: " + var9);}this.eventPublisher.publishAuthenticationFailure(new BadCredentialsException(var9.getMessage(), var9), new PreAuthenticatedAuthenticationToken("access-token", "N/A"));this.authenticationEntryPoint.commence(request, response, new InsufficientAuthenticationException(var9.getMessage(), var9));return;}chain.doFilter(request, response);}//省略......
}
2.4.3.2 BearerTokenExtractor

从请求 Header中获取token

protected String extractHeaderToken(HttpServletRequest request) {Enumeration headers = request.getHeaders("Authorization");String value;do {if (!headers.hasMoreElements()) {return null;}value = (String)headers.nextElement();} while(!value.toLowerCase().startsWith("Bearer".toLowerCase()));String authHeaderValue = value.substring("Bearer".length()).trim();request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE, value.substring(0, "Bearer".length()).trim());int commaIndex = authHeaderValue.indexOf(44);if (commaIndex > 0) {authHeaderValue = authHeaderValue.substring(0, commaIndex);}return authHeaderValue;}
2.4.3.3 OAuth2AuthenticationManager

资源服务认证token校验实现
程序片段:

public Authentication authenticate(Authentication authentication) throws AuthenticationException {if (authentication == null) {throw new InvalidTokenException("Invalid token (token not found)");} else {String token = (String)authentication.getPrincipal();//1. 从验证token存储介质获取请求传递的Access Token获取对应的验证信息OAuth2Authentication auth = this.tokenServices.loadAuthentication(token);if (auth == null) {throw new InvalidTokenException("Invalid token: " + token);} else {//2. 验证token并加载验证信息Collection<String> resourceIds = auth.getOAuth2Request().getResourceIds();if (this.resourceId != null && resourceIds != null && !resourceIds.isEmpty() && !resourceIds.contains(this.resourceId)) {throw new OAuth2AccessDeniedException("Invalid token does not contain resource id (" + this.resourceId + ")");} else {this.checkClientDetails(auth);if (authentication.getDetails() instanceof OAuth2AuthenticationDetails) {OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails)authentication.getDetails();if (!details.equals(auth.getDetails())) {details.setDecodedDetails(auth.getDetails());}}auth.setDetails(authentication.getDetails());auth.setAuthenticated(true);return auth;}}}}

3、OAuth2 扩展

3.1 自定义异常处理

3.1.1 自定义授权端点处理异常

授权服务器中的错误处理使用标准的Spring MVC功能,即@ExceptionHandler端点本身中的方法。但是其原生的异常信息可能与我们实际使用的异常处理不一致,需要进行转义。可以自定义WebResponseExceptionTranslator,想授权端点提供异常处理,这是更改响应异常处理的最佳方法。

//省略
@Autowiredprivate WebResponseExceptionTranslator webResponseExceptionTranslator;/*** 自定义授权服务配置* 使用密码模式需要配置*/@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) {endpoints.authenticationManager(authenticationManager).userDetailsService(userService).exceptionTranslator(webResponseExceptionTranslator);//}
//省略/*** 实现WebResponseExceptionTranslator接口,自定义授权端点异常处理*/
@Component
public class CustomOAuth2WebResponseExceptionTranslator implements WebResponseExceptionTranslator {private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer();@Overridepublic ResponseEntity<OAuth2Exception> translate(Exception e) throws Exception {Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(e);Exception ase = (OAuth2Exception)this.throwableAnalyzer.getFirstThrowableOfType(OAuth2Exception.class, causeChain);if (ase != null) {return this.handleOAuth2Exception((OAuth2Exception)ase);}ase = (AuthenticationException)this.throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain);if (ase != null) {return this.handleOAuth2Exception(new UnauthorizedException(e.getMessage(), e));}ase = (AccessDeniedException)this.throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain);if (ase instanceof AccessDeniedException) {return this.handleOAuth2Exception(new ForbiddenException(ase.getMessage(), ase));}ase = (HttpRequestMethodNotSupportedException)this.throwableAnalyzer.getFirstThrowableOfType(HttpRequestMethodNotSupportedException.class, causeChain);if(ase instanceof HttpRequestMethodNotSupportedException){return this.handleOAuth2Exception(new MethodNotAllowed(ase.getMessage(), ase));}return this.handleOAuth2Exception(new UnsupportedResponseTypeException("服务内部错误", e));}private ResponseEntity<OAuth2Exception> handleOAuth2Exception(OAuth2Exception e) throws IOException {int status = e.getHttpErrorCode();HttpHeaders headers = new HttpHeaders();headers.set("Cache-Control", "no-store");headers.set("Pragma", "no-cache");if (status == HttpStatus.UNAUTHORIZED.value() || e instanceof InsufficientScopeException) {headers.set("WWW-Authenticate", String.format("%s %s", "Bearer", e.getSummary()));}CustomOauthException exception = new CustomOauthException(e.getMessage(),e);ResponseEntity<OAuth2Exception> response = new ResponseEntity(exception, headers, HttpStatus.valueOf(status));return response;}
//省略

3.1.2 自定义匿名用户访问无权限资源时的异常

当访问未纳入Oauth2保护资源或者访问授权端点时客户端验证失败,抛出异常,AuthenticationEntryPoint. Commence(…)就会被调用。这个对应的代码在ExceptionTranslationFilter中,当ExceptionTranslationFilter catch到异常后,就会间接调用AuthenticationEntryPoint。默认使用LoginUrlAuthenticationEntryPoint处理异常,当抛出依次LoginUrlAuthenticationEntryPoint会将异常呈现给授权服务器默认的Login视图。

  • 访问未纳入Oauth2资源管理的接口
    当访问未纳入Oauth2资源管理的接口时,因为应用接入安全框架,因此依旧会进行权限验证,当用户无权访问时会有ExceptionTranslationFilter 拦截异常并将异常呈现到默认的登录视图提示用户登录:
    未受保护资源异常

  • 调用授权端点,客户端校验失败
    当调用授权端点(/oauth/token)时,根据前面的源码我们知道在授权认证前,会先通过客户端验证Filter进行客户端验证,当客户端验证失败会抛出异常并由ExceptionTranslationFilter 拦截,将异常呈现给默认的登录视图:
    客户端验证失败

源码分析:

//顶层授权认证异常处理Point 
package org.springframework.security.web;import ...public interface AuthenticationEntryPoint {void commence(HttpServletRequest var1, HttpServletResponse var2, AuthenticationException var3) throws IOException, ServletException;
}

当ExceptionTranslationFilter catch到异常后,就会间接调用AuthenticationEntryPoint。

package org.springframework.security.web.access;import ...
public class ExceptionTranslationFilter extends GenericFilterBean {//省略......public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest)req;HttpServletResponse response = (HttpServletResponse)res;try {chain.doFilter(request, response);this.logger.debug("Chain processed normally");} catch (IOException var9) {throw var9;} catch (Exception var10) {Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(var10);RuntimeException ase = (AuthenticationException)this.throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain);if (ase == null) {ase = (AccessDeniedException)this.throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain);}if (ase == null) {if (var10 instanceof ServletException) {throw (ServletException)var10;}if (var10 instanceof RuntimeException) {throw (RuntimeException)var10;}throw new RuntimeException(var10);}if (response.isCommitted()) {throw new ServletException("Unable to handle the Spring Security Exception because the response is already committed.", var10);}//异常处理,间接调用AuthenticationEntryPoint.commencethis.handleSpringSecurityException(request, response, chain, (RuntimeException)ase);}}//省略......private void handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response, FilterChain chain, RuntimeException exception) throws IOException, ServletException {if (exception instanceof AuthenticationException) {this.logger.debug("Authentication exception occurred; redirecting to authentication entry point", exception);this.sendStartAuthentication(request, response, chain, (AuthenticationException)exception);} else if (exception instanceof AccessDeniedException) {Authentication authentication = SecurityContextHolder.getContext().getAuthentication();if (!this.authenticationTrustResolver.isAnonymous(authentication) && !this.authenticationTrustResolver.isRememberMe(authentication)) {this.logger.debug("Access is denied (user is not anonymous); delegating to AccessDeniedHandler", exception);this.accessDeniedHandler.handle(request, response, (AccessDeniedException)exception);} else {this.logger.debug("Access is denied (user is " + (this.authenticationTrustResolver.isAnonymous(authentication) ? "anonymous" : "not fully authenticated") + "); redirecting to authentication entry point", exception);this.sendStartAuthentication(request, response, chain, new InsufficientAuthenticationException(this.messages.getMessage("ExceptionTranslationFilter.insufficientAuthentication", "Full authentication is required to access this resource")));}}}异常处理,间接调用AuthenticationEntryPoint.commenceprotected void sendStartAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, AuthenticationException reason) throws ServletException, IOException {SecurityContextHolder.getContext().setAuthentication((Authentication)null);this.requestCache.saveRequest(request, response);this.logger.debug("Calling Authentication entry point.");this.authenticationEntryPoint.commence(request, response, reason);}//省略......//默认的异常处理,会将异常呈现给默认的Login视图
package org.springframework.security.web.authentication;import ...public class LoginUrlAuthenticationEntryPoint implements AuthenticationEntryPoint, InitializingBean {//省略...public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {String redirectUrl = null;if (this.useForward) {if (this.forceHttps && "http".equals(request.getScheme())) {redirectUrl = this.buildHttpsRedirectUrlForRequest(request);}if (redirectUrl == null) {String loginForm = this.determineUrlToUseForThisRequest(request, response, authException);if (logger.isDebugEnabled()) {logger.debug("Server side forward to: " + loginForm);}RequestDispatcher dispatcher = request.getRequestDispatcher(loginForm);dispatcher.forward(request, response);return;}} else {redirectUrl = this.buildRedirectUrlToLoginPage(request, response, authException);}this.redirectStrategy.sendRedirect(request, response, redirectUrl);}//省略

默认的视图呈现异常肯定不符合我们实际的应用,因此需要多此类异常进行自定义处理。

package com.easy.mall.exception;import ...@Component
@AllArgsConstructor
public class CustomAuthExceptionEntryPoint implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest httpServletRequest,HttpServletResponse response, AuthenticationException e)throws IOException, ServletException {response.setCharacterEncoding(StandardCharsets.UTF_8.name());response.setContentType(MediaType.APPLICATION_JSON_VALUE);CommonResult<String> result = CommonResult.failed();result.setCode(HttpStatus.HTTP_UNAUTHORIZED);if (e != null) {result.setMessage("unauthorized");result.setData(e.getMessage());}response.setStatus(HttpStatus.HTTP_UNAUTHORIZED);PrintWriter printWriter = response.getWriter();printWriter.append(JSONObject.toJSONString(result));}
}@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}@Overridepublic void configure(HttpSecurity http) throws Exception {http.csrf().disable().authorizeRequests().antMatchers("/oauth/**", "/login/**", "/logout/**").permitAll().anyRequest().authenticated().and().formLogin().permitAll();//web 安全控制添加注册自定义的错误处理http.exceptionHandling().authenticationEntryPoint(new CustomAuthExceptionEntryPoint());}
}
  • 自定义异常处理后的效果

客户端验证失败

访问未纳入OAuth2受保护资源接口

3.1.3 自定义受OAuth2令牌保护的资源认证失败异常

受OAuth2令牌保护的资源无权限访问异常时,异常由原生的Oauth2authenticationentrypoint处理,但是其原生的异常信息可能与我们实际使用的异常处理不一致,需要进行转义。

  • 原生的异常信息响应:
{"error": "invalid_token","error_description": "Invalid access token: 1"
}
  • 自定义异常
@Configuration
@EnableResourceServer
public class Oauth2SourceConfig extends ResourceServerConfigurerAdapter {@Overridepublic void configure(HttpSecurity http) throws Exception {http.authorizeRequests().anyRequest().authenticated().and().requestMatchers().antMatchers("/user/**");//配置需要保护的资源路径}@Overridepublic void configure(ResourceServerSecurityConfigurer resources) throws Exception {resources.authenticationEntryPoint(new CustomAuthExceptionEntryPoint());//自定义受令牌保护资源服务异常处理}
}
  • 自定义异常处理效果
{"code": 401,"data": "Invalid access token: 1","message": "unauthorized"
}

3.1.4 密码认证自定义异常信息

思路

通过上一章源码分析知道,Oauth2.0账号密码认证交由AuthenticationManager进行处理,其认证链路AuthenticationManager->ProviderManager->AuthenticationProvider->AbstractUserDetailsAuthenticationProvider->DaoAuthenticationProvider,其中AbstractUserDetailsAuthenticationProvider和DaoAuthenticationProvider类是密码认证得实际处理类,当密码认证异常时,其返回得异常信息并不满足我们实际业务需求。需要根据业务需求进行重构。重构代码主要是重新实现:

  • AbstractUserDetailsAuthenticationProvider.authenticate
  • DaoAuthenticationProvider.additionalAuthenticationChecks

代码清单:

  1. CustomAuthenticationProvider 继承DaoAuthenticationProvider,重写密码认证逻辑
@Slf4j
public class CustomAuthenticationProvider extends DaoAuthenticationProvider {private UserDetailsChecker preAuthenticationChecks = new CustomAuthenticationProvider.DefaultPreAuthenticationChecks();private UserDetailsChecker postAuthenticationChecks = new CustomAuthenticationProvider.DefaultPostAuthenticationChecks();private boolean forcePrincipalAsString = false;public CustomAuthenticationProvider() {}private UserCache userCache = new NullUserCache();/*** 重新密码校验实现-自定义异常处理* @param userDetails* @param authentication* @throws AuthenticationException*/@Overrideprotected void additionalAuthenticationChecks(UserDetails userDetails,UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {if (authentication.getCredentials() == null) {this.logger.debug("Authentication failed: no credentials provided");throw new BadCredentialsException(Resources.getMessage("STATUSCODE_21004","can not get credentials."));} else {String presentedPassword = authentication.getCredentials().toString();PasswordEncoder passwordEncoder = SpringContextUtil.getBean("bCryptPasswordEncoder");if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {this.logger.debug("Authentication failed: password does not match stored value");throw new BadCredentialsException(Resources.getMessage("STATUSCODE_21001","username or password error."));}}}/*** 重新认证核心方法-自定义返回异常* @param authentication* @return* @throws AuthenticationException*/@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, () -> {return this.messages.getMessage("CustomAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported");});String username = authentication.getPrincipal() == null ? "NONE_PROVIDED" : authentication.getName();boolean cacheWasUsed = true;UserDetails user = this.userCache.getUserFromCache(username);if (user == null) {cacheWasUsed = false;try {user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);} catch (UsernameNotFoundException var6) {this.logger.debug("User '" + username + "' not found");if (this.hideUserNotFoundExceptions) {throw new BadCredentialsException(Resources.getMessage("STATUSCODE_21000","account not exist!"));}throw var6;}Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");}try {this.preAuthenticationChecks.check(user);this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);} catch (AuthenticationException var7) {if (!cacheWasUsed) {throw var7;}cacheWasUsed = false;user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);this.preAuthenticationChecks.check(user);this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);}this.postAuthenticationChecks.check(user);if (!cacheWasUsed) {this.userCache.putUserInCache(user);}Object principalToReturn = user;if (this.forcePrincipalAsString) {principalToReturn = user.getUsername();}return this.createSuccessAuthentication(principalToReturn, authentication, user);}private class DefaultPostAuthenticationChecks implements UserDetailsChecker {private DefaultPostAuthenticationChecks() {}public void check(UserDetails user) {if (!user.isCredentialsNonExpired()) {log.debug("User account credentials have expired");throw new CredentialsExpiredException(Resources.getMessage("STATUSCODE_21005","user credentials have expired."));}}}private class DefaultPreAuthenticationChecks implements UserDetailsChecker {private DefaultPreAuthenticationChecks() {}public void check(UserDetails user) {if (!user.isAccountNonLocked()) {CustomAuthenticationProvider.this.logger.debug("User account is locked");throw new LockedException(Resources.getMessage("STATUSCODE_21006","user account is locked."));} else if (!user.isEnabled()) {CustomAuthenticationProvider.this.logger.debug("User account is disabled");throw new DisabledException(Resources.getMessage("STATUSCODE_21002","user is disabled."));} else if (!user.isAccountNonExpired()) {CustomAuthenticationProvider.this.logger.debug("User account is expired");throw new AccountExpiredException(Resources.getMessage("STATUSCODE_21008","user account has expired."));}}}}

3.1.5 Security自定义异常分析总结

根据上述一系列源码分析,我们知道Security是通过一系列Filter过滤链实现授权认证,不同情况和场景其过滤链不一样,因此当出现异常也通常由不同的异常处理器进行处理,因此需要针对不同情况进行自定义处理。

附录

  • 1 构造Basic Auth认证头
 /*** 构造Basic Auth认证头信息* * @return*/private String getHeader() {String auth = APP_KEY + ":" + SECRET_KEY;byte[] encodedAuth = Base64.encodeBase64(auth.getBytes(Charset.forName("US-ASCII")));String authHeader = "Basic " + new String(encodedAuth);return authHeader;}
    1. 修改WEB安全配置,引入自定义密码认证处理器
@Configuration
@EnableWebSecurity
@Import({CustomAuthenticationEntryPoint.class, CustomAccessDeniedHandler.class})
public class SecurityBrowserConfig extends WebSecurityConfigurerAdapter {//省略/*** 定义自定义密码认证处理器* @return*/@BeanCustomAuthenticationProvider customAuthenticationProvider() {CustomAuthenticationProvider customAuthenticationProvider = new CustomAuthenticationProvider();customAuthenticationProvider.setUserDetailsService(userDetailsService());return customAuthenticationProvider;}/*** 将自定义密码认证处理器注册到AuthenticationManager* @return* @throws Exception*/@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {ProviderManager manager = new ProviderManager(Arrays.asList(customAuthenticationProvider()));return manager;}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/345906.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

接口(API)开发,测试工具-apifox

前言 为什么需要接口&#xff08;API&#xff09;? 因为不同的平台或系统可能使用不同的技术栈、编程语言或数据格式。API提供了一个标准化的方式&#xff0c;使得这些不同的系统可以相互交换数据和功能调用&#xff0c;实现互操作性 在开发日常的项目交互中&#xff0c;不…

内网穿透的方式有哪些——快解析的优势

外网穿透内网技术&#xff0c;即内网映射&#xff0c;是把目标本地内网地址和端口发布到互联网&#xff0c;是一种由内网开放到外网的权限操作。那么&#xff0c;内网穿透的方法有哪些呢&#xff1f;做映射外网的方法。需要结合自己本地网络环境和应用场景来实施。这里分享三种…

Python Excel 指定内容修改

需求描述 在处理Excel 自动化时,财务部门经常有一个繁琐的场景,需要读取分发的Excel文件内容复制到汇总Excel文件对应的单元格内,如下图所示: 这种需求可以延申为,财务同事制作一个模板,将模板发送给各员工,财务同事需收取邮件将员工填写的excel文件下载到本机,再类似…

[FreeRTOS 基础知识] 任务调度 与 链表

文章目录 任务并行的概念RTOS如何实现多任务调度&#xff1f; 任务并行的概念 在生活中&#xff0c;经常出现一心多用的情况。比如你需要一边吃饭一边手机回复信息&#xff0c;这里面就存在两个任务&#xff1a;任务一、吃饭。任务二、手机回复信息。 假如你无法一心多用&…

FreeRTOS实时系统 在任务中增加数组等相关操作 导致单片机起不来或者挂掉

在调试串口任务中增加如下代码&#xff0c;发现可以用keil进行仿真&#xff0c;但是烧录程序后&#xff0c;调试串口没有打印&#xff0c;状态灯也不闪烁&#xff0c;单片机完全起不来 博主就纳了闷了&#xff0c;究竟是什么原因&#xff0c;这段代码可是公司永流传的老代码了&…

平面设计神器CorelDRAW2021精简版,你值得拥有!

亲爱的设计师小伙伴们&#xff0c;今天我要为大家种草一款神奇的软件——CorelDRAW平面设计软件2021精简版&#xff01;&#x1f929;✨作为一名专业的图形设计师&#xff0c;我深知一个好工具对于我们的工作有多么重要。而这款软件简直就是我们设计师的救星&#xff01;&#…

【MySQL数据库】:MySQL索引特性

目录 索引的概念 磁盘 磁盘的基本特征 MySQL与磁盘交互的基本单位 索引的理解 建立测试表 理解单个Page 理解多个Page 页目录 单页情况 多页情况 索引的数据结构 聚簇索引 VS 非聚簇索引 索引操作 创建主键索引 创建唯一索引 创建普通索引 创建全文索引 查询…

每日一题——Python实现PAT乙级1099 性感素数(举一反三+思想解读+逐步优化)

一个认为一切根源都是“自己不够强”的INTJ 个人主页&#xff1a;用哲学编程-CSDN博客专栏&#xff1a;每日一题——举一反三Python编程学习Python内置函数 Python-3.12.0文档解读 目录 我的写法 专业点评 时间复杂度分析 空间复杂度分析 综合点评 我要更强 优化点 …

力扣每日一题130:被围绕的区域

题目 中等 相关标签 相关企业 给你一个 m x n 的矩阵 board &#xff0c;由若干字符 X 和 O &#xff0c;找到所有被 X 围绕的区域&#xff0c;并将这些区域里所有的 O 用 X 填充。 示例 1&#xff1a; 输入&#xff1a;board [["X","X","X"…

U盘文件系统结构损坏的应对与预防

在数字化时代&#xff0c;U盘作为便携式存储设备&#xff0c;其重要性不言而喻。然而&#xff0c;当U盘文件系统结构损坏时&#xff0c;我们可能会面临数据丢失的风险。本文将深入探讨U盘文件系统结构损坏的问题&#xff0c;分析其产生的原因&#xff0c;并给出相应的数据恢复方…

基于pytoch卷积神经网络水质图像分类实战

具体怎么学习pytorch&#xff0c;看b站刘二大人的视频。 完整代码&#xff1a; import numpy as np import os from PIL import Image import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data…

时序数据库是Niche Market吗?

引言 DB-Engines的流行程度排行从其评估标准[4]可以看出完全不能够做为市场规模的评估标准。甚至于在知道市场规模后可以用这个排行作为一个避雷手册。毕竟现存市场小&#xff0c;可预见增长规模小&#xff0c;竞争大&#xff0c;创新不足&#xff0c;那只能卷价格&#xff0c…

【文献阅读】LORA: LOW-RANK ADAPTATION OF LARGE LANGUAGE MODELS

目录 1. motivation2. overall3. model3.1 low rank parametrized update matrices3.2 applying lora to transformer 4. limitation5. experiment6. 代码7. 补充参考文献 1. motivation 常规的adaptation需要的微调成本过大现有方法的不足&#xff1a; Adapter Layers Introd…

.net core 使用js,.net core 使用javascript,在.net core项目中怎么使用javascript

.net core 使用js&#xff0c;.net core 使用javascript&#xff0c;在.net core项目中怎么使用javascript 我项目里需要用到“文字编码”&#xff0c;为了保证前端和后端的编码解码不处bug, 所以&#xff0c;我在项目中用了这个 下面推荐之前在.net F4.0时的方法 文章一&#…

操作系统真象还原:中断

第7章-中断 这是一个网站有所有小节的代码实现&#xff0c;同时也包含了Bochs等文件 7.2操作系统是中断驱动的 没有中断&#xff0c;操作系统几乎什么都做不了&#xff0c;操作系统是中断驱动。 7.3中断分类 7.3.1外部中断 外部中断是指来自 CPU 外部的中断&#xff0c;而…

Python基础教程(八):迭代器与生成器编程

&#x1f49d;&#x1f49d;&#x1f49d;首先&#xff0c;欢迎各位来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里不仅可以有所收获&#xff0c;同时也能感受到一份轻松欢乐的氛围&#xff0c;祝你生活愉快&#xff01; &#x1f49d;&#x1f49…

时隔很久运行苍穹外卖项目,出现很多错误

中途运行了很多其他项目&#xff0c;maven的配置文件还被我修改了一次。导致再次运行苍穹外卖项目出现很多错误。 发现没有办法&#xff0c;把本地的仓库删了个干干净净。然后点击clean发现报错&#xff1a; Cannot access alimaven (http://mavejavascript:void(0);n.aliyun.…

力扣每日一题 6/10

881.救生艇[中等] 题目&#xff1a; 给定数组 people 。people[i]表示第 i 个人的体重 &#xff0c;船的数量不限&#xff0c;每艘船可以承载的最大重量为 limit。 每艘船最多可同时载两人&#xff0c;但条件是这些人的重量之和最多为 limit。 返回 承载所有人所需的最小船…

Vue15-watch对比计算属性

一、姓名案例 1-1、watch实现 1-2、计算属性 对比发现&#xff1a; 计算属性比watch属性更简略一些。 1-3、计算属性 VS 侦听属性 1-4、需求变更 计算属性中不能开启异步任务&#xff01;&#xff01;&#xff01;因为计算属性靠return返回值。但是watch靠亲自写代码去改。 1-…

streamlit:如何快速构建一个应用,不会前端也能写出好看的界面

通过本文你可以了解到&#xff1a; 如何安装streamlit&#xff0c;运行起来第一个demo熟悉streamlit的基本语法&#xff0c;常用的一些组件使用streamlit库构建应用 大模型学习参考&#xff1a; 大模型学习资料整理&#xff1a;如何从0到1学习大模型&#xff0c;搭建个人或企业…