Spring boot OAuth2 JWT的认证中心

说明 本文较长,要想直接使用脚手架,请移步至github

Oauth2概要

这里只是做一个简单的重要介绍,让你可以看得懂代码中这个字段属性的含义是什么。想了解更加详细的请看Oauth2.0介绍

Oauth2的基本流程以及角色说明

OAuth2流程图

角色说明

client:第三方应用(即App或向外提供接口)
Resource Owner:资源所有者(即用户)
Authentication Server:授权认证服务(发配Access Token)
Resource Server:资源服务器(存储用户资源信息等资源)

流程说明(基于code模式)

1.第三方应用请求用户授权;
2.用户同意授权,并返回一个授权码(code);
3.第三方应用根据授权码(code)向授权认证服务进行授权;
4.授权服务器根据授权码(code),校验通过,并返回给第三方应用令牌(Access Token);
5.第三方应用根据令牌(Access Token)向资源服务请求相关资源;
6.资源服务器验证令牌(Access Token),校验通过,并返回第三方所请求的资源。

Oauth2客户端的授权模式

客户端必须得到用户的授权(authorization grant),才能获得令牌(access token)。OAuth 2.0定义了四种授权方式。

授权码模式(authorization code)
简化模式(implicit)
密码模式(resource owner password credentials)
客户端模式(client credentials
以上的四种模式非常重要,在代码中都会有体现。而且经常会出现。

JWT概要

JWT原理

JWT的原理是,客户端请求服务器,服务器收到请求后,会生成一个JSON对象,发回给用户,就像下面。

{
	"username":"a",
	"role":"b",
	....
}

当然不可能是明文的,服务器在生成这个对象的时候,会加上签名(具体的请自行百度)。以后,用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。服务器就不保存任何 session 数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展。

Spring Security OAuth2的使用

Spring boot 开发Oauth2以及JWT可以使用Spring Security OAuth2。
下面我们看一下请求Oauth2示例(基于Postman)
请求地址是你的IP:端口/oauth/token ( /oauth/token )照抄

设置Authorization
在设置中我们需要输入Username和Password,这两个值非常重要,分别代表
Username: 客户端的的唯一标识
Password: 客户端的标识凭证
我们还需要设置几项。
设置Headers
设置Body
这里body设置了四个值
socpe: 用户的范围
grant_type: OAuth2客户端授权模式,我这里是password模式
username: 用户的用户名(和Authorization不同)
password: 用户的密码(和Authorization不同)

在以上的请求示例中我们看到了6个字段需要我们去设置分别是
Username
Password
socpe
grant_type
username
password
我们是如何设置的这几个值的呢, Spring Security OAuth2已经为我们封装好了。我们只需要继承AuthorizationServerConfigurerAdapter即可实现相关既可以。

1. AuthorizationServerConfigurerAdapter
1.1 第三方客户端基本信息配置说明

AuthorizationServerConfigurerAdapter这个是类用来配置第三方客户端详情的,token的存储方式以及token的生成方式。
先看Username、Password、socpe、grant_type怎么设置,
我们通过继承 AuthorizationServerConfigurerAdapter需要重写一个方法来设置这四个。代码如下

@Configuration
@AllArgsConstructor
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

    /**
     * 数据源
     */
   // private final DataSource dataSource;
    /**
     * 加密方式
     */
    private final BCryptPasswordEncoder passwordEncoder;
    /**
     * @param clients 用户的相关配置载体
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
       String secret = passwordEncoder.encode("secret");
        //配置客户端
        clients.
                //使用内存设置
                        inMemory()
                //设置clientID
                .withClient("client")
                //client_secret 要进行加密
                .secret(secret)
                //授权类型
                .authorizedGrantTypes("password", "refresh_token")
                //授权范围
                .scopes("read,writer");
        //clients.jdbc(dataSource);
        super.configure(clients);
    }
}

以上重写方法是基于jvm内存来设置的,如设置数据库相关,直接clients.jdbc(dataSource)就可以,同时在数据库添加以下表

DROP TABLE IF EXISTS `oauth_client_details`;
CREATE TABLE `oauth_client_details` (
  `client_id` varchar(256) 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;

INSERT INTO oauth_client_details (
	client_id,
	resource_ids,
	client_secret,
	scope,
	authorized_grant_types,
	web_server_redirect_uri,
	authorities,
	access_token_validity,
	refresh_token_validity,
	additional_information,
	autoapprove
)
VALUES
	(
	    'client',
	    NULL,
	    '{noop}secret',
	    'all',
	    'password,authorization_code,refresh_token,implicit,client_credentials',
	    NULL,
		NULL,
		NULL,
		NULL,
		NULL,
		'true'
	);

看到了client_id就是我们的Username,client_secret就是我们的Password,scope对应着socpe,authorized_grant_types对应着grant_type注意这里的grant_type为了刷新token还有一种模式

  • refresh_token:获取access token时附带的用于刷新新的token模式

上面已经四个属性已经都有了,接下来就是用户名和密码了。
设置用户名和密码通过实现UserDetailsService这个类
下面是代码

@Component
public class AquamanUserDetailsService  implements UserDetailsService {

    //这里需要mapper
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // TODO 这个地方可以通过username从数据库获取正确的用户信息,包括密码和权限等。
        List<GrantedAuthority> grantedAuthorityList = new ArrayList<>();
        //grantedAuthorityList.add(new MyGrantedAuthority("MY_ROLE1", "MY_MENU1"));
        grantedAuthorityList.add(new SimpleGrantedAuthority("ROLE_USER"));
        grantedAuthorityList.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
        //admin123加密后$2a$10$WIDPnAxDUfQq/asUAnMkROiAHUEvkJ/eI9TWlyCoa6QMW2YJUkNEW
        return new User(username, "$2a$10$WIDPnAxDUfQq/asUAnMkROiAHUEvkJ/eI9TWlyCoa6QMW2YJUkNEW", grantedAuthorityList);
    }
}

还是实现AuthorizationServerConfigurerAdapter这个类,重写以下方法,将实现的UserDetailsService 设置到AuthorizationServerEndpointsConfigurer 即可。

@Configuration
@AllArgsConstructor
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

    /**
     * oauth2模式相关的东西
     */
    private final AuthenticationManager authenticationManagerBean;
    /**
     * 加密方式
     */
    private final BCryptPasswordEncoder passwordEncoder;
    /**
     * 用户相关东西
     */
    private final AquamanUserDetailsService userDetailsService;
    /**
     * 用来配置拦截的相关东西
     *
     * @param endpoints 请求的拦截点
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManagerBean)//使用密码模式
                .userDetailsService(userDetailsService) //不添加的话会出错
                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)//设置允许的请求方式
               // .tokenStore(tokenStore())//设置tokenStore存储在哪里
        //.tokenEnhancer(tokenEnhancer())//设置token的增强器
                //.accessTokenConverter(jwtAccessTokenConverter())
        ;
        super.configure(endpoints);
    }   
}

以上就是OAuth2对应的每个字段的代码对应。

1.2 token的存储以及token的生成相关
1.2.1 TokenStore

创建AccessToken完之后,除了发放给第三方,肯定还得保存起来,才可以使用。因此,TokenStore为我们完成这一操作,将令牌(AccessToken)保存或持久化。
TokenStore也有一个默认的实现类InMemoryTokenStore,从名字就知道是通过保存到内存进而实现保存Access Token。
TokenStore的实现有多种类型,可以根据业务需求更改Access Token的保存类型:

InMemoryTokenStore: 这个是OAuth2默认采用的实现方式。在单服务上可以体现出很好特效(即并发量不大,并且它在失败的时候不会进行备份),大多项目都可以采用此方法。毕竟存在内存,而不是磁盘中,调试简易。
JdbcTokenStore: 这个是基于JDBC的实现,令牌(Access Token)会保存到数据库。这个方式,可以在多个服务之间实现令牌共享。
RedisTokenStore: 基于redis的存储token,保存后是二进制格式的。
JwtTokenStore: jwt全称 JSON Web Token。这个实现方式不用管如何进行存储(内存或磁盘),因为它可以把相关信息数据编码存放在令牌里。JwtTokenStore 不会保存任何数据,但是它在转换令牌值以及授权信息方面与 DefaultTokenServices 所扮演的角色是一样的。但有两个缺点:

  • 撤销一个已经授权的令牌会很困难,因此只适用于处理一个生命周期较短的以及撤销刷新令牌。
  • 令牌占用空间大,如果加入太多用户凭证信息,会存在传输冗余
1.2.2 JWT Token

想使用jwt令牌,需要在授权服务中配置JwtTokenStore。之前说了,jwt将一些信息数据编码后存放在令牌,那么其实在传输的时候是很不安全的,所以Spring OAuth2提供了JwtAccessTokenConverter来怼令牌进行编码和解码。适用JwtAccessTokenConverter可以自定义秘签(SigningKey)。SigningKey用处就是在授权认证服务器生成进行签名编码,在资源获取服务器根据SigningKey解码校验。

  @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter(){
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("aquaman");
        return converter;
    }
1.2.3 Token 增强器
@Bean   
 public TokenEnhancer tokenEnhancer() {
        return (oAuth2AccessToken, oAuth2Authentication) -> {
            final Map<String, Object> additionalInfo = new HashMap<>();
            additionalInfo.put("license", "aquaman");
            ((DefaultOAuth2AccessToken) oAuth2AccessToken).setAdditionalInformation(additionalInfo);
            return oAuth2AccessToken;
        };
    }
1.2.4 端点接入-endpoints

我们在前面设置用户的信息时介绍过这个类下面是一个详细的介绍
1.)端点(endpoints)的相关属性配置:

  • authenticationManager:认证管理器。若我们上面的Grant Type设置为password,则需设置一个AuthenticationManager对象

  • userDetailsService:若是我们实现了UserDetailsService,来管理用户信息,那么得设我们的userDetailsService对象

  • authorizationCodeServices:授权码服务。若我们上面的Grant Type设置为authorization_code,那么得设一个AuthorizationCodeServices对象

  • tokenStore:这个就是我们上面说到,把我们想要是实现的Access Token类型设置

  • accessTokenConverter:Access Token的编码器。也就是JwtAccessTokenConverter

  • tokenEnhancer:token的拓展。当使用jwt时候,可以实现TokenEnhancer来进行jwt对包含信息的拓展

  • tokenGranter:当默认的Grant Type已经不够我们业务逻辑,实现TokenGranter 接口,授权将会由我们控制,并且忽略Grant Type的几个属性。

2).端点(endpoints)的授权url:
要授权认证,肯定得由url请求,才可以传输。因此OAuth2提供了配置授权端点的URL。
AuthorizationServerEndpointsConfigurer ,还是这个配置对象进行配置,其中由一个pathMapping()方法进行配置授权端点URL路径,默认提供了两个参数defaultPath和customPath:

public AuthorizationServerEndpointsConfigurer pathMapping(String defaultPath, String customPath) {
		this.patternMap.put(defaultPath, customPath);
		return this;
}

pathMapping的defaultPath有:

/oauth/authorize:授权端点
/oauth/token:令牌端点
/oauth/confirm_access:用户确认授权提交端点
/oauth/error:授权服务错误信息端点
/oauth/check_token:用于资源服务访问的令牌解析端点
/oauth/token_key:提供公有密匙的端点,如果使用JWT令牌的话

以上就是AuthorizationServerConfigurerAdapter的一些相关的使用。设置以上以上的所有还不能成为一个认证中心。还需要设置继承一个类,再次配置用户名和密码的。

2 WebSecurityConfigurerAdapter

这个类比较简单用来配置用户名和密码就可以,同时还可以设置忽略某个接口的拦截等,与AuthorizationServerConfigurerAdapter相互配合

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Autowired
    private AquamanUserDetailsService userDetailsService;

    @Override
    public void configure(WebSecurity web) throws Exception {
        // 将 check_token 暴露出去,否则资源服务器访问时报 403 错误
        web.ignoring().antMatchers("/oauth/check_token");
        super.configure(web);
    }

	    /**
     * 设置模式相关
     *
     * @return 模式相关
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * 设置加密方式
     *
     * @return 加密方式
     */
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        //使用默认加密方式
        return new BCryptPasswordEncoder();
    }

    /**
     * 设置用户的信息
     *
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
        super.configure(auth);
    }
}
3 ResourceServerConfigurerAdapter

ResourceServerConfigurerAdapter使用来设置资源服务器相关的,资源服务器就是存放一些受令牌保护的资源,只有令牌并且有效正确才能获取到资源。 内部是通过Spring OAuth2的Spring Security Authentication filter 的过滤链来进行保护。如果服务即是授权中心,又是资源服务器。只需要进行如下的配置

@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/**").authenticated()//所有服务都需要保护,来检查token否有效
                .anyRequest().authenticated();
    }
}

如果授权服务和资源服务器是分开的,不仅要以上的配置还需要配置,检查验证token相关的东西。而Jwt方式和其他方式是不同的。
如果是JWT的需要在application.yml进行如下配置

security:
  oauth2:
    resource:
      jwt:
        key-value: aquaman 

这里的key-value的值是在设置JWT Token时jwtAccessTokenConverter 的setSigningKey(“aquaman”)属性。
如果不是JWT的方式加一下配置

security:
  oauth2:
    client:
      client-id: client
      client-secret: secret
      scope: all
      access-token-uri:  http://授权服务IP:授权服务端口/oauth/token
      user-authorization-uri: http://授权服务IP:授权服务端口/oauth/authorize
    resource:
      token-info-uri: http://授权服务IP:授权服务端口/oauth/check_token
  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个基于Spring Boot Security、OAuth2和JWT实现的示例代码: 1. 添加Maven依赖 在pom.xml文件中添加以下依赖: ``` <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2</artifactId> <version>2.3.7.RELEASE</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> ``` 2. 配置Spring Security 在Spring Boot应用程序中,您可以通过@Configuration配置类来配置Spring Security。以下是一个示例配置类: ``` @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Autowired private PasswordEncoder passwordEncoder; @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers("/oauth/**").permitAll() .antMatchers("/api/**").authenticated() .and() .formLogin().permitAll() .and() .logout().permitAll(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder); } } ``` 在上面的配置类中,我们禁用了CSRF保护,并配置了访问权限。具体来说: - /oauth/** URL模式应该允许所有人访问,因为我们将使用OAuth2协议进行身份验证和授权。 - /api/** URL模式应该需要身份验证。 - 我们还配置了表单登录和注销。 3. 配置OAuth2 在Spring Boot应用程序中,您可以使用@Configuration配置类来配置OAuth2。以下是一个示例配置类: ``` @Configuration @EnableAuthorizationServer public class OAuth2Config extends AuthorizationServerConfigurerAdapter { @Autowired private AuthenticationManager authenticationManager; @Autowired private UserDetailsService userDetailsService; @Autowired private PasswordEncoder passwordEncoder; @Bean public JwtAccessTokenConverter jwtAccessTokenConverter() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); converter.setSigningKey("my-signing-key"); // 设置JWT签名密钥 return converter; } @Bean public JwtTokenStore jwtTokenStore() { return new JwtTokenStore(jwtAccessTokenConverter()); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient("my-client-id") .secret(passwordEncoder.encode("my-client-secret")) .authorizedGrantTypes("password", "refresh_token") .scopes("read", "write") .accessTokenValiditySeconds(3600) .refreshTokenValiditySeconds(86400); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.authenticationManager(authenticationManager) .userDetailsService(userDetailsService) .accessTokenConverter(jwtAccessTokenConverter()) .tokenStore(jwtTokenStore()); } } ``` 在上面的配置类中,我们使用@EnableAuthorizationServer注释启用OAuth2,并实现了AuthorizationServerConfigurer接口以配置客户端和端点。具体来说: - 我们配置了一个内存中的客户端,使用密码和刷新令牌授权类型,以及读写作用域。 - 我们还配置了JWT令牌转换器和令牌存储。 - 我们将身份验证管理器、用户详细信息服务、JWT令牌转换器和令牌存储配置为端点。 4. 实现用户详细信息服务 我们需要实现UserDetailsService接口来加载用户详细信息。以下是一个示例实现: ``` @Service public class UserDetailsServiceImpl implements UserDetailsService { @Autowired private UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsername(username); if (user == null) { throw new UsernameNotFoundException("User not found with username: " + username); } return new org.springframework.security.core.userdetails.User( user.getUsername(), user.getPassword(), Collections.emptyList()); } } ``` 在上面的实现中,我们使用Spring Data JPA从数据库中加载用户,并创建一个SimpleGrantedAuthority对象列表作为用户的权限。 5. 实现密码编码器 我们需要实现PasswordEncoder接口,以便在创建用户时对密码进行编码。以下是一个示例实现: ``` @Service public class PasswordEncoderImpl implements PasswordEncoder { @Override public String encode(CharSequence rawPassword) { return BCrypt.hashpw(rawPassword.toString(), BCrypt.gensalt()); } @Override public boolean matches(CharSequence rawPassword, String encodedPassword) { return BCrypt.checkpw(rawPassword.toString(), encodedPassword); } } ``` 在上面的实现中,我们使用BCrypt编码算法对密码进行编码和验证。 6. 实现控制器 最后,我们需要实现一个控制器来测试OAuth2和JWT。以下是一个示例实现: ``` @RestController @RequestMapping("/api") public class ApiController { @GetMapping("/hello") public String hello() { return "Hello, World!"; } @GetMapping("/user") public Principal user(Principal principal) { return principal; } } ``` 在上面的实现中,我们有两个端点:/api/hello和/api/user。前者返回一个简单的字符串,后者返回当前用户的Principal对象。 7. 测试应用程序 现在,您可以启动应用程序并使用以下步骤测试OAuth2和JWT: - 获取访问令牌。使用以下curl命令以密码授权方式获取访问令牌: ``` curl -X POST \ -d 'grant_type=password&username=my-username&password=my-password' \ -H 'Authorization: Basic bXktY2xpZW50LWlkOm15LWNsaWVudC1zZWNyZXQ=' \ http://localhost:8080/oauth/token ``` 在上面的命令中,my-username和my-password应该是您的用户名和密码,bXktY2xpZW50LWlkOm15LWNsaWVudC1zZWNyZXQ=应该是Base64编码的客户端ID和客户端密钥。 - 使用访问令牌访问API。使用以下curl命令使用JWT访问/api/hello端点: ``` curl -H 'Authorization: Bearer <access-token>' \ http://localhost:8080/api/hello ``` 在上面的命令中,<access-token>应该是您在第一步中获取的访问令牌。 - 获取用户信息。使用以下curl命令使用JWT访问/api/user端点: ``` curl -H 'Authorization: Bearer <access-token>' \ http://localhost:8080/api/user ``` 在上面的命令中,<access-token>应该是您在第一步中获取的访问令牌。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值