spring-security-oauth2.0的使用入门

oauth2.0也是开发中用的比较多的一种授权机制,它主要用来颁发令牌;OAuth 引入了一个授权层,用来分离两种不同的角色:客户端和资源所有者。......资源所有者同意以后,资源服务器可以向客户端颁发令牌。客户端通过令牌,去请求数据。

通过上面对oauth的介绍,我们已经大概知道它的用途了,现在由于各种原因oauth1几乎已经没人再用了,目前用的较多的是oauth2.0; oauth2.0规定了四种获得令牌的流程,如下所示:

  • 授权码(authorization-code)
  • 隐藏式(implicit)
  • 密码式(password):
  • 客户端凭证(client credentials)

对于后端开发来说授权码的模式是使用较多的也相对安全一些,本次 分享的也就是该模式了

1 开发前的准备

由于我们使用的是spring security oauth2.0,鉴于此官方也为我们提供了oauth2.0开发相关参考如用到的表结构、整合代码等,下面是官方提供的表结构

https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql

数据表的字段释义我也找了相关的资料可以参考下面这篇文章:

https://blog.csdn.net/wangxuelei036/article/details/109491215

但是这些sql直接在mysql里执行是要报错的,也是一个小小的坑,报错的地方就是“token LONGVARBINARY“里定义的这个longvarbinary了,原因是mysql并不支持这种数据类型,解决的方式很简单就是把它全部改成blob数据类型就可以了

稍作修改之后,执行这些sql生成表到自己的数据库里就ok了,此外我们只需要在项目引入maven的依赖准备工作就完成了

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

如果项目已经有security的依赖那么只需要引入oauth2的就可以了

2  oauth服务端的开发

(1) 服务端也需要使用spring security,毫无疑问我们需要先配置spring security

package com.debug.security;

import com.debug.service.SecurityUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private SecurityUserService securityUserService;

    @Bean
    public BCryptPasswordEncoder myPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginProcessingUrl("/login")
                .permitAll()//指定认证页面可以匿名访问 //关闭跨站请求防护
                .and().csrf().disable();
    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception { //UserDetailsService类
        auth.userDetailsService(securityUserService)
                .passwordEncoder(myPasswordEncoder());
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

这里的spring security配置就比较简单了,只是指定了密码加密、认证url等,认证登录也可以定义成自己的,这里方便起见就直接用spring security默认的

(2) 认证授权的代码逻辑,客观来说此处只用到认证,代码如下:

package com.debug.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.debug.entity.TSystemPermission;
import com.debug.entity.TSystemRole;
import com.debug.entity.TSystemUser;
import com.debug.security.MyAuthenticationProvider;
import com.debug.service.SecurityUserService;
import com.debug.service.TSystemPermissionService;
import com.debug.service.TSystemRoleService;
import com.debug.service.TSystemUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class SecurityUserServiceImpl implements SecurityUserService {

    @Autowired
    private TSystemUserService tSystemUserService;


    @Autowired
    private TSystemRoleService tSystemRoleService;

    @Autowired
    private TSystemPermissionService tSystemPermissionService;


    @Autowired
    private MyAuthenticationProvider myAuthenticationProvider;

    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        QueryWrapper<TSystemUser> qw = new QueryWrapper<TSystemUser>();
        qw.eq("login_name", username);
        TSystemUser user = tSystemUserService.getOne(qw);
        if (user == null) {
            throw new UsernameNotFoundException("用户名不存在");
        }
        List<TSystemPermission> permissionList = tSystemPermissionService.getRolePermission(user.getId(), "1");
        List<TSystemRole> roleList = tSystemRoleService.getUserRole(user.getId());

        String roleName = roleList.get(0).getName();

        StringBuffer buf = new StringBuffer();
        for (TSystemPermission permission : permissionList) {
            String per = permission.getPermission();
            buf.append(per + ",");

        }

        String sp = buf.toString().substring(0, buf.toString().lastIndexOf(","));
        List<GrantedAuthority> authList = AuthorityUtils.commaSeparatedStringToAuthorityList(roleName + "," + sp);

        UserDetails u = new User(user.getLoginName(), user.getPassword(), authList);

        return u;
    }

    public String login(String username, String password) {

        QueryWrapper<TSystemUser> qw = new QueryWrapper<TSystemUser>();
        qw.eq("login_name", username);
        TSystemUser user = tSystemUserService.getOne(qw);

        // 这里我们还要判断密码是否正确,这里我们的密码使用BCryptPasswordEncoder进行加密的
        if (!new BCryptPasswordEncoder().matches(password, user.getPassword())) {
            throw new BadCredentialsException("密码不正确");
        }

        //return jwtTokenUtil.generateToken(username);
        return null;
    }

    public String refreshToken(String oldToken) {
        String token = oldToken;
        return "error";
    }
}

认证的代码和昨天写的是一样的,几乎没有任何修改

(3) oauth服务端的配置

这一步的配置相对麻烦一点,我们需要配置Oauth的数据源(和之前新建的oauth开头的那几张表有关)、token保存策略、授权信息保存策略、客户端登录信息来源等

package com.debug.config;

import com.debug.service.SecurityUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.approval.ApprovalStore;
import org.springframework.security.oauth2.provider.approval.JdbcApprovalStore;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;

import javax.sql.DataSource;

@Configuration
@EnableAuthorizationServer
public class OauthServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private DataSource dataSource;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private SecurityUserService securityUserService;

    //从数据库中查询出客户端信息
    @Bean
    public JdbcClientDetailsService clientDetailsService() {
        return new JdbcClientDetailsService(dataSource);
    }

    //token保存策略
    @Bean
    public TokenStore tokenStore() {
        return new JdbcTokenStore(dataSource);
    }

    //授权信息保存策略
    @Bean
    public ApprovalStore approvalStore() {
        return new JdbcApprovalStore(dataSource);
    }

    //授权码模式专用对象
    @Bean
    public AuthorizationCodeServices authorizationCodeServices() {
        return new JdbcAuthorizationCodeServices(dataSource);
    }

    //指定客户端登录信息来源
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(clientDetailsService());
    }

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

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                .userDetailsService(securityUserService)
                .approvalStore(approvalStore())
                .authenticationManager(authenticationManager)
                .authorizationCodeServices(authorizationCodeServices())
                .tokenStore(tokenStore());
    }


}

3 oauth资源端的开发

资源端的开发除了加入资源端相关配置外,其他地方和普通的spring boot项目无异,先来看一下资源端的配置

package com.debug.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;

import javax.sql.DataSource;

@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(securedEnabled = true)
public class OauthSourceConfig extends ResourceServerConfigurerAdapter {
    @Autowired
    private DataSource dataSource;

    /*** TokenStore是OAuth2保存token的接口 * 其下有RedisTokenStore保存到redis中, * JdbcTokenStore保存到数据库中, * InMemoryTokenStore保存到内存中等实现类, * 这里我们选择保存在数据库中 * @return */
    @Bean
    public TokenStore tokenStore() {
        return new JdbcTokenStore(dataSource);
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        TokenStore tokenStore = new JdbcTokenStore(dataSource);
        resources.resourceId("product_api") //指定当前资源的id,非常重要!必须写!
                .tokenStore(tokenStore); //指定保存token的方式
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests() //指定不同请求方式访问资源所需要的权限,一般查询是read,其余是write。
                .antMatchers(HttpMethod.GET, "/**").access("#oauth2.hasScope('read')")
                .antMatchers(HttpMethod.POST, "/**").access("#oauth2.hasScope('write')")
                .antMatchers(HttpMethod.PATCH, "/**").access("#oauth2.hasScope('write')")
                .antMatchers(HttpMethod.PUT, "/**").access("#oauth2.hasScope('write')")
                .antMatchers(HttpMethod.DELETE, "/**").access("#oauth2.hasScope('write')")
                .and().headers().addHeaderWriter((request, response) -> {
            response.addHeader("Access-Control-Allow-Origin", "*");//允许跨域
            if (request.getMethod().equals("OPTIONS")) {//如果是跨域的预检请求,则原封不动向下传达请 求头信息
                response.setHeader("Access-Control-Allow-Methods", request.getHeader("Access- Control-Request-Method"));
                response.setHeader("Access-Control-Allow-Headers", request.getHeader("Access- Control-Request-Headers"));
            }
        });
    }
}

最需要注意的就是这个 资源端ID了 ,这个ID除了在代码中配置也需要添加到数据库中,如下所示,在oauth_client_details表里加入一条记录,sql如下:

INSERT INTO `oauth_client_details` VALUES ('debugxwz', 'product_api', '$2a$10$WZQaLHfS6amrJzN50wE3e.upn8KIi1wmCH9FSdZE6OBt8OKSyGLm.', 'read, write', 'client_credentials,implicit,authorization_code,refresh_token,password', 'http://www.baidu.com', NULL, NULL, NULL, NULL, 'false');

client_id和client_secret在后面的获取token接口中需要使用到所以不能写错,密码的生成也是使用springsecurity的那一套,这里不做过多解释

接下来编写一个测试用的controller,代码如下:

package com.debug.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/product")
public class ProductController {
    @GetMapping
    public String findAll() {
        return "查询产品列表成功!";
    }
}

4 测试

先在浏览器敲 http://localhost:8082/oauth/authorize?response_type=code&client_id=debugxwz

这一步的作用是获取code,在获取code之前需要经过spring security,根据配置会使用spring security提供的登录页面,输入我们的账号和密码(不是oauth_client_details的)

登录后浏览器跳转到一个询问页面,就类似微信授权那个

点击下面的授权按钮则跳转到我们制定的url并携带code , 此处为了方便数据库跳转链接直接写的百度,日常开发就换成自己的并保证外网可以访问到就行

到此为止我们就取得code了

接下来我们使用postman来获取token,url地址为 http://localhost:8082/oauth/token

需要post提交的参数有四个分别是grant_type(此处填写authorization_code)、client_id、client_secret、code 如下所示:

到此为止我们就获取到access_token了,接下来就可以拿这个access_token去访问资源了(刚才的ProductController),地址如下:

http://localhost:8081/product?access_token=e0ac892e-bbee-4ba6-b348-88d43e66de8a

到此为止一个简单oauth2.0服务端和资源端的代码就开发完成了

其他参考资料:http://www.ruanyifeng.com/blog/2019/04/oauth-grant-types.html

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值