oauth2 +jwt,password密码授权模式 spring boot 2.x

看到我的文章,是你的幸运。 刚搭建完oauth2框架,使用密码模式。这篇文章是实战。 踩了不少坑,终于把oauth2的概念理解透,实战框架搭建起来,最下面是有github上的demo例子。 跟着我的教程,你能迅速搭建一个支持oauth2的spring-boot应用。

oauth2概念介绍

0Auth, 开放授权(open Authorization)是一种资源提供商用于授权第三方应用代表资源所有者获取有限访问权限的授权机制。整个授权期间,第三方应用都碰不到用户的密码,所以OAuth是安全的。
那么,它是怎么实现的呢?
先介绍oauth2的包含的角色:

  1. resource owner 资源所有者,通常指用户
  2. resource server 资源服务器。想要访问的东东。比如网页所在的服务器
  3. Client 客户端,第三方应用,用户直接访问的网站。比如csdn
  4. Authorization Server 授权服务器 在这里获取token。比如QQ的登录页面

举一个例子:
你(资源所有者)用浏览器,打开csdn首页(客户端);点击登录,选择qq方式登录;跳转到
qq登录页面(授权服务器);qq登录页面需要你输入短信验证码;输入完毕,授权完成,
跳转到csdn(客户端)的服务器指定url(原因是qq授权服务器和csdn签了合约,授权完成后回到csdn网站),此时授权服务器会把token和refresh_token都带给csdn(客户端);
csdn使用token,去qq的个人信息服务器(资源服务器),获取用户信息;
从而csdn就获取到用户信息,和自己的db库数据比较,就知道是否是注册用户。

上面的例子,是授权码模式。是最全面,最复杂的方式。为什么叫授权码模式,想想我们平时用到的短信验证码,微信公众号码。
总共有4种模式

有4种授权模式

  1. 授权码模式(例子是我们常见的短信验证码)
  2. 隐式授权模式 (较少使用,一般是客户端非常信任的情况下使用,比如移动qq客户端)
  3. 密码模式(客户端 比较信任,一般属于同一个公司的内网)
  4. 客户端授权模式(这种模式没有用户,非常信任客户端)

4种授权码模式介绍
这是大佬的博客。要仔细看,理解oauth2的4种模式,也就理解这个框架,到底干什么的?spring security做了什么事情。

接下来实战:

先下载我的github项目。oauthDemo

搭建一个spring-boot项目。官方链接

maven依赖

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
</parent>

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.security</groupId>
  <artifactId>spring-security-test</artifactId>
  <scope>test</scope>
</dependency>

<dependency>
  <groupId>org.springframework.security.oauth.boot</groupId>
  <artifactId>spring-security-oauth2-autoconfigure</artifactId>
  <version>2.0.8.RELEASE</version>
</dependency>

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>

oauth2配置类AuthorizationServerConfigurerAdapter

我的实现类是AuthorizationServerConfig
需要开启@EnableAuthorizationServer注解

这里配置开启验证,看注释吧

/**
     *
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
//        super.configure(security);
        // 开启该配置,才可以使用oauth2认证,否则模式默认是HTTP的基本认证 
        security.allowFormAuthenticationForClients();
        // 这两个配置,目的是开启两个端点url,默认服务器是关闭的 /oauth/token_key /oauth/check_token
        security.tokenKeyAccess("permitAll()").checkTokenAccess("permitAll()");
    }

这里是配置token的参数。inMemory是一种简单的使用模式。大规模使用,肯定要研究一下源码。提供了复杂的使用。一般来说,框架是提供了关于jdbc相关的类,来做类似的事情。
参数解析自行百度关键字。

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                //client_id
                .withClient("client-for-server")
                .secret(passwordEncoder.encode("client"))
//                .secret("client")
                .authorizedGrantTypes("authorization_code", "implicit", "password", "refresh_token")
                .accessTokenValiditySeconds(60 * 6 * 2)
                .refreshTokenValiditySeconds(60 * 60 * 2)
//                .redirectUris("http://localhost:8080/login/oauth2/code/authorizationserver")
                .redirectUris("http://www.baidu.com")
                .additionalInformation()
                //这里有疑问,资源id
//                .resourceIds(ResourceServerConfig.RESOURCE_ID)
//                .authorities("ROLE_CLIENT")
                .scopes("any");
//                .autoApprove("profile");
    }

这里是配置token的类型,密码模式

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
//        endpoints.authenticationManager(authenticationManager).tokenStore(memoryTokenStore()).userDetailsService(userDetailsService);

        endpoints.authenticationManager(authenticationManager).allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
//        endpoints.tokenStore(memoryTokenStore());
        endpoints.tokenStore(tokenStore());
        endpoints.accessTokenConverter(accessTokenConverter());
//        endpoints.userDetailsService(userDetailsService);
    }

配置oauth2的链路Filter ResourceServerConfigurerAdapter

我的实现类是ResourceServerConfig
开启@EnableResourceServer

最终要是覆盖这个方法。requestMatchers().antMatchers("/resources/*")是
1,筛选/resources下的path
2,这个path下的anyRequest都要验证

提示:这是配置那些url要做oauth验证的,需要仔细理解。尤其是antMatchers和authorizeRequests间的前后关系。可以自行关键字搜索***

    @Override
    public void configure(HttpSecurity http) throws Exception {
       logger.info("ResourceServerConfig中配置HttpSecurity对象执行");
       //只有/me端点作为资源服务器的资源
//        http.requestMatchers().antMatchers("/")
//                .and()
//                .authorizeRequests()
//                .antMatchers("/")
//                .permitAll()
//                .anyRequest()
//                .authenticated();

        http.requestMatchers().antMatchers("/resources/*").and().authorizeRequests().anyRequest().authenticated();
    }

一般spring Security web Filter配置 WebSecurityConfigurerAdapter

没有oauth2框架下,spring security是使用WebSecurityConfigurerAdapter来做filter配置的,我的实现类是SecurityConfiguration
需要开启注解@EnableWebSecurity

关键配置,是过滤那些url需要验证。模式是需要HTTP BASIC认证


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        logger.info("SecurityConfiguration中配置HttpSecurity对象执行");
//        http.authorizeRequests().antMatchers("/admin").permitAll().anyRequest().hasAnyRole("USER", "ADMIN").and().formLogin();;
        http.authorizeRequests().antMatchers("/").permitAll().anyRequest().hasAnyRole("USER", "ADMIN")
                .and().formLogin();
    }

关键配置,是配置用户的账号密码。看到inMemory,就知道它仅仅存在于内存中。所以实际生产,是需要使用jdbc相关的接口的,获取真实的数据


    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//        auth.userDetailsService(userDetailsService());
        auth.inMemoryAuthentication().withUser("user").password(passwordEncoder().encode("user")).roles("USER")
                .and()
                .withUser("admin").password(passwordEncoder().encode("admin")).roles("ADMIN");
    }

介绍antMatchers()和authorizeRequests().antMatchers()区别

首先要介绍ResourceServerConfigurerAdapter和WebSecurityConfigurerAdapter间的区别
区别介绍
这篇文章,其中介绍了antMatchers()和authorizeRequests().antMatchers()的区别
下面是一个例子:
http.requestMatchers().antMatchers("/resources/*").and().authorizeRequests().antMatchers("/resources/phone").permitAll().anyRequest().authenticated();

第一步:筛选url是否前缀是/resources,不是则代表oauth2无需处理这类请求;是则下一步
第二部:查看path是否/resources/phone,是则无需授权认证,直接通过;不是则下一步
第三步:其他任何请求anyRequest,都要授权认证

第一步解析,假如不归oauth2认证,那么请求就会跑到WebSecurityConfigurerAdapter上认证。
这样就实现了既有一部分是oauth2认证;剩下的,是session认证。

至此,oauth2的配置已经完成。
假如只需要oauth2框架,其实SecurityConfiguration是不需要配置的,因为它默认是用session的会话认证。
而oauth2框架的目的是使用token认证。token认证(使用jwt)是服务端无状态的认证;而session认证是有状态认证。对于一个固定的url请求,spring框架里面,授权认证两者只能2选1。
好,那为什么要配置SecurityConfiguration,原因就在这里。
很多时候,一个服务器,一部分请求要走oauth2认证,一部分要走session认证。那么这里就凸显出链路过滤的配置。请看configure(HttpSecurity http),理解透它。

至此,oauth2已经配置完毕。

实践oauth2授权认证

端点(endPoint),在源码里面,可以经常看到这个单词。可以理解为授权场景的url吧。
ResourceServerConfigurerAdapter 确定了需要oauth2认证的endPoint。
这是我的demo例子
ResourceController

@RestController
@RequestMapping("/resources")
public class ResourceController {
    private static final Logger logger = LoggerFactory.getLogger(ResourceController.class);

    /**
     * 需要认证
     *
     * @param access_token
     * @param principal
     * @return
     */
    @RequestMapping("/me")
    public Principal me(String access_token, Principal principal) {
        logger.info(principal.toString());
        logger.info(access_token);
        return principal;
    }

    /**
     * antMatchers("/resources/phone").permitAll()
     * 无需认证
     *
     * @return
     */
    @RequestMapping("/phone")
    public String phone() {

        return "phone: 1234567890";
    }
}

调用/resources/phone 直接通过。
在这里插入图片描述

没有token直接调用,会返回401错误。/resources/me
在这里插入图片描述

oauth2获取token的方式

spring security oauth2 中的 endpoint

  • /oauth/authorize(授权端,授权码模式使用)
  • /oauth/token(令牌端,获取 token)
  • /oauth/check_token(资源服务器用来校验token)
  • /oauth/confirm_access(用户发送确认授权)
  • /oauth/error(认证失败)
  • /oauth/token_key(如果使用JWT,可以获的公钥用于 token 的验签)

本文介绍密码模式,只需要用到/oauth/token和/oauth/check_token
密码模式获取token
在这里插入图片描述

参数介绍,直接看源码
org.springframework.security.oauth2.provider.OAuth2Request

public class OAuth2Request extends BaseRequest implements Serializable {

	private static final long serialVersionUID = 1L;

	/**
	 * Resolved resource IDs. This set may change during request processing.
	 */
	private Set<String> resourceIds = new HashSet<String>();

	/**
	 * Resolved granted authorities for this request. May change during request processing.
	 */
	private Collection<? extends GrantedAuthority> authorities = new HashSet<GrantedAuthority>();

	/**
	 * Whether the request has been approved by the end user (or other process). This will be altered by the User
	 * Approval Endpoint and/or the UserApprovalHandler as appropriate.
	 */
	private boolean approved = false;

	/**
	 * Will be non-null if the request is for a token to be refreshed (the original grant type might still be available
	 * via {@link #getGrantType()}).
	 */
	private TokenRequest refresh = null;

	/**
	 * The resolved redirect URI of this request. A URI may be present in the original request, in the
	 * authorizationParameters, or it may not be provided, in which case it will be defaulted (by processing classes) to
	 * the Client's default registered value.
	 */
	private String redirectUri;

	/**
	 * Resolved requested response types initialized (by the OAuth2RequestFactory) with the response types originally
	 * requested.
	 */
	private Set<String> responseTypes = new HashSet<String>();

	/**
	 * Extension point for custom processing classes which may wish to store additional information about the OAuth2
	 * request. Since this class is serializable, all members of this map must also be serializable.
	 */
	private Map<String, Serializable> extensions = new HashMap<String, Serializable>();

验证token有效性
在这里插入图片描述

token过期后提示
在这里插入图片描述

业务请求带上token访问
在这里插入图片描述

至此。已经试验完毕。

补充使用jwt

JSON Web Token - 在Web应用间安全地传递信息
理解JWT的使用场景和优劣

我的demo使用jwt的方式:

    // 使用最基本的InMemoryTokenStore生成token
    @Bean
    public TokenStore memoryTokenStore() {
        return new InMemoryTokenStore();
    }

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    /**
     * 指定使用token的类型,默认的Opaque格式和jwt格式
     * @return
     */
    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        final JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        //这里使用堆成加密,123456789是work key
        converter.setSigningKey("123456789");
//        KeyStoreKeyFactory keyStoreKeyFactory =
//                new KeyStoreKeyFactory(new ClassPathResource("mytest.jks"), "mypass".toCharArray());
//        converter.setKeyPair(keyStoreKeyFactory.getKeyPair("mytest"));
//        converter.setAccessTokenConverter(new CustomerAccessTokenConverter());
        return converter;
    }

后记

这个demo,resource server 资源服务器和Authorization Server 授权服务器是部署在一起的。可以搭建两个spring-boot,拆分功能。
我的GitHub下载:hjq2016 oauth2Demo

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个基于Spring Boot SecurityOAuth2和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 SecuritySpring 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、付费专栏及课程。

余额充值