SpringSecurityOAuth2(1)(password,authorization_code,refresh_token,client_credentials)获取token

最近项目准备使用SpringSecurityOAuth2做权限认证管理,所以先了解一下SpringSecurityOAuth的使用原理并做一个demo做参考 

GitHub地址

码云地址

一、项目知识准备

  1. 什么是OAuth2

    OAuth 2.0 的标准是 RFC 6749 文件 OAuth 引入了一个授权层,用来分离两种不同的角色:客户端和资源所有者。......资源所有者同意以后,资源服务器可以向客户端颁发令牌。客户端通过令牌,去请求数据:

  2. OAuth2核心-授权

  3. OAuth2的四种方式

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

4. 握手流程(摘自RFC6749)

5. SpringSecurity

基于Spring的企业应用系统提供声明式的安全訪问控制解决方式的安全框架(简单说是对访问权限进行控制嘛),应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分

*6. SpringSecurity 中默认的内置过滤列表:

SpringSecurity 中默认的内置过滤列表*

(其他后续补充)

二、项目准备

1. 环境

 SpringBoot 2.1.0.RELEASE jdk1.8   SpringCloud Greenwich.SR1  consul服务发现与注册 
  1. 项目
项目描述
fp-commonsjar公用
fp-gateway网关
fp-authorization-serverOAuth2认证服务器
3. 项目搭建
(1)网关搭建 frame-gateway

    pom.xml:
        引入依赖文件;在启动器上添加 注册监听@EnableDiscoveryClient 
        并注入 DiscoveryClientRouteDefinitionLocator 
    application.xml: 
        spring-main-allow-bean-definition-overriding: true (相同名称注入允许覆盖)
        spring.application.name:SpringCloud-consul-gateway   (设置应用名称必须)
        bootstrap.yml 中配置 consul(比application先加载) 
        prefer-ip-address: true (使用ip注册,有些网络会出现用主机名来获取注册的问题)

(2)fp-commons jar公用

    pom.xml:
          引入依赖文件;添加一些公用方法

(3)fp-authorization-server

(git中提交了一个至简版本的OAuth2认证中心,只有token生成功能,没有资源保护以及认证)

    只需要配置AuthorizationServerConfigurerAdapter 认证服务器
    以及WebSecurityConfigurerAdapter SpringSecurity配置 两个文件,就能实现token生成。
    如果没有需要保护的资源不用ResourceServerConfigurerAdapter 资源服务器配置

三、认证服务器搭建

基本的SpringBoot搭建略过,这里只介绍SpringSecurity OAuth2的token实现,基本的数据处理以及其他看源码。

首先完成ClientDetailsService的自定义实现(获取客户端相关信息)

/**
* @Description 自定义客户端数据
* @Author wwz
* @Date 2019/07/28
* @Param
* @Return
*/
@Service
public class MyClientDetailsService implements ClientDetailsService {
    @Autowired
    private AuthClientDetailsMapper authClientDetailsMapper;
    @Override
    public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException {
        AuthClientDetails clientDetails = authClientDetailsMapper.selectClientDetailsByClientId(clientId);
        if (clientDetails == null) {
            throw new ClientRegistrationException("该客户端不存在");
        }
        MyClientDetails details = new MyClientDetails(clientDetails);
        return details;
    }
}

以及UserDetailsService的自定义实现(获取用户相关信息)

/**
 * @Description 自定义用户验证数据
 * @Author wwz
 * @Date 2019/07/28
 * @Param
 * @Return
 */
@Service
public class MyUserDetailsService implements UserDetailsService {

    @Autowired
    private AuthUserMapper authUserMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 自定义用户权限数据
        AuthUser authUser = authUserMapper.selectByUsername(username);
        if (authUser == null) {
            throw new UsernameNotFoundException("用户名不存在");
        }
        if (!authUser.getValid()) {
            throw new UsernameNotFoundException("用户不可用");
        }
        Set<GrantedAuthority> grantedAuthorities = new HashSet<>();
        if (authUser.getAuthRoles() != null) {
            for (AuthRole role : authUser.getAuthRoles()) {
                // 当前角色可用
                if (role.getValid()) {
                    //角色必须是ROLE_开头
                    GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(role.getRoleName());
                    grantedAuthorities.add(grantedAuthority);
                    if (role.getAuthPermissions() != null) {
                        for (AuthPermission permission : role.getAuthPermissions()) {
                            // 当前权限可用
                            if (permission.getValid()) {
                                // 拥有权限设置为   auth/member/GET  可以访问auth服务下面 member的查询方法
                                GrantedAuthority authority = new SimpleGrantedAuthority(permission.getServicePrefix() + "/" + permission.getUri() + "/" + permission.getMethod());
                                grantedAuthorities.add(authority);
                            }
                        }
                    }
                }
                //获取权限
            }
        }
        MyUserDetails userDetails = new MyUserDetails(authUser, grantedAuthorities);
        return userDetails;
    }
}

然后新建认证服务配置MySecurityOAuth2Config,继承AuthorizationServerConfigurerAdapter

/**
 * @Description OAuth2认证服务配置
 * @Author wwz
 * @Date 2019/07/28
 * @Param
 * @Return
 */
@Configuration
@EnableAuthorizationServer
public class MySecurityOAuth2Config extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private AuthenticationManager authenticationManager;    // 认证方法入口

    @Autowired
    private RedisConnectionFactory connectionFactory;  // redis连接工厂

    @Autowired
    private MyUserDetailsService userDetailsService;  // 自定义用户验证数据

    @Autowired
    private MyClientDetailsService clientDetailsService; // 自定义客户端数据
    // 加密方式
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /***
     * 设置token用redis保存
     */
    @Bean
    public TokenStore tokenStore() {
        //token保存在redis中(也可以保存在数据库、内存中new InMemoryTokenStore()、或者jwt;)。
        //如果保存在中间件(数据库、Redis),那么资源服务器与认证服务器可以不在同一个工程中。
        //注意:如果不保存access_token,则没法通过access_token取得用户信息
        RedisTokenStore redis = new RedisTokenStore(connectionFactory);
        return redis;
    }

    /**
     * 配置令牌端点(Token Endpoint)的安全约束
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        oauthServer
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()")
                .allowFormAuthenticationForClients();   // 允许表单登录
    }

    /**
     * 配置 oauth_client_details【client_id和client_secret等】信息的认证【检查ClientDetails的合法性】服务
     * 设置 认证信息的来源:数据库,内存,也可以自己实现ClientDetailsService的loadClientByClientId 方法自定义数据源
     * 自动注入:ClientDetailsService的实现类 JdbcClientDetailsService (检查 ClientDetails 对象)
     * 这个方法主要是用于校验注册的第三方客户端的信息,可以存储在数据库中,默认方式是存储在内存中
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 设置从自定义接口获取客户端信息
        clients.withClientDetails(clientDetailsService);
// clients.inMemory() // 使用in-memory存储
//                .withClient("client_name") // client_id
//                .secret(passwordEncoder().encode("111")) // client_secret
//                .redirectUris("http://localhost:8001")
//                // 该client允许的授权类型
//                .authorizedGrantTypes("password", "authorization_code", "refresh_token", "client_credentials")
//                .scopes("app", "app1", "app3"); // 允许的授权范围
    }

    /**
     * 配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                .tokenStore(tokenStore())  // 配置token存储
                .userDetailsService(userDetailsService)  // 配置自定义的用户权限数据,不配置会导致token无法刷新
                .authenticationManager(authenticationManager)
                .tokenServices(defaultTokenServices());      // 加载token配置

    }

    /**
     * 把认证的token保存到redis
     * <p>注意,自定义TokenServices的时候,需要设置@Primary,否则报错,</p>
     * 自定义的token
     * 认证的token是存到redis里的 若ClientDetails中设定了有效时间则以设定值为准
     */
    @Primary
    @Bean
    public DefaultTokenServices defaultTokenServices() {
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(tokenStore());
        tokenServices.setSupportRefreshToken(true);
        tokenServices.setClientDetailsService(clientDetailsService);
        tokenServices.setAccessTokenValiditySeconds(60 * 60 * 12);  // token有效期自定义设置,默认12小时
        tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7);  // refresh_token默认30天
        return tokenServices;
    }
}

接着新建Security的配置MySecurityConfig 继承WebSecurityConfigurerAdapter

**
 * @Description security 配置
 * ResourceServerConfigurerAdapter 是比WebSecurityConfigurerAdapter 的优先级低的
 * @Author wwz
 * @Date 2019/07/28
 * @Param
 * @Return
 */
@Configuration
@EnableWebSecurity
@Order(2)  // WebSecurityConfigurerAdapter 默认为100 这里配置为2设置比资源认证器高
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
    // 自定义用户验证数据
    @Bean
    public MyUserDetailsService userDetailsService() {
        return new MyUserDetailsService();
    }

    // 加密方式
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    // 验证器加载
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                // 匹配oauth相关,匹配健康,匹配默认登录登出 在httpSecurity处理,,其他到ResourceServerConfigurerAdapter OAuth2处理  1
                .requestMatchers().antMatchers("/oauth/**", "/actuator/health", "/login", "/logout")
                .and()
                // 匹配的全部无条件通过 permitAll 2
                .authorizeRequests().antMatchers("/oauth/**", "/actuator/health", "/login", "/logout").permitAll()
                // 匹配条件1的 并且不再条件2通过范围内的其他url全部需要验证登录
                .and().authorizeRequests().anyRequest().authenticated()
                // 启用登录验证
                .and().formLogin().permitAll();
        // 不启用 跨站请求伪造 默认为启用, 需要启用的话得在form表单中生成一个_csrf
        http.csrf().disable();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());
    }
}

到此,SpringSecurity OAuth2至简的项目搭建完成,下一步为4种token的基本验证

四、4种token获取(这里使用postman做token获取)

  1. password模式认证

请求地址:

http://192.168.3.14:8001/auth/oauth/token?grant_type=password&username=username&password=111

参数:

参数名说明
grant_type类型必须为password
username用户名
password密码
client_id客户端id
client_serce客户端密码
请求:

响应: 返回信息 包括 access_token 通行token, refresh_token 重新申请token,scope资源范围

验证:

无权访问页面

访问页面

  1. client_credentials模式

请求地址:

http://192.168.3.14:8001/auth/oauth/token?grant_type=client_credentials&client_id=client_name&client_secret=111

参数:

参数名说明
grant_type类型必须为client_credentials
client_id客户端id
client_serce客户端密码
请求

响应:返回参数 包括access_token,scope 权限范围 (但是不包含refresh_token)

验证:默认无任何权限 当然,这里的权限也可以在ClientDetails中的authorities字段中默认增加权限

  1. refresh_token 模式

    refresh_token 只有请求过一次并且获取到refresh_token才能用该值重新申请可用token,并且,申请了新的token后,原先为过期的token立即失效,refresh_token作为新的token的refresh_token继续使用。

请求地址:

http://192.168.3.14:8001/auth/oauth/token?grant_type=refresh_token&refresh_token=384b02e7-66d5-4d5b-8179-53793f7ba40b

参数

参数名说明
grant_type类型必须为refresh_token
refresh_token刷新token

请求:

响应:返回结果包括 新的token,原refresh_token,scope权限

  1. authorization_code模式

请求:

第一次请求:http://192.168.3.14:8001/auth/oauth/authorize?response_type=code&client_id=client_name&redirect_uri=http://192.168.3.14:8001/auth/oauth/token&scope=auth
第二次请求:http://192.168.3.14:8001/auth/oauth/token?grant_type=authorization_code&redirect_uri=http://192.168.3.14:8001/auth/oauth/token&code=mbeBvY

参数:

第一次参数

参数名说明
response_type类型必须为code
client_id客户端id
redirect_uri返回code地址 与后端client_Id对应 redirect_uri,与第二步请求token对应

第二次参数

参数名说明
grant_type类型必须为authorization_code
code为返回code 这里为 mbeBvY
redirect_uri返回code地址 上一步redirect_uri http://192.168.3.14:8001/auth/oauth/token

上述,填错一个就会报错code只能使用一次 再次请求失效

第一次请求后跳转 登录界面

登录后跳转授权页面然后返回到携带的redirect_uri路径 并带上code

第二次请求

响应:返回结果包括 token, refresh_token,scope权限

4种模式到此结束,SpringSecurityOAuth2的至简代码到此为止,后续关于自定义响应,url权限判断,jwt形式等后续文章会详细描述

转载于:https://my.oschina.net/u/3500033/blog/3080885

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
OAuth2 授权过程中,`grant_type` 是必须的参数,它表示授权类型。常见的授权类型包括 `authorization_code`,`password`,`client_credentials`,`refresh_token` 等。 在 Java 中发送 HTTP 请求时,可以使用 `HttpClient` 或者 `HttpURLConnection` 等类库。以下是使用 `HttpClient` 发送 HTTP POST 请求,设置 `grant_type` 参数的示例代码: ``` import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.message.BasicNameValuePair; import org.apache.http.NameValuePair; import java.util.ArrayList; import java.util.List; public class HttpClientExample { public static void main(String[] args) throws Exception { CloseableHttpClient httpclient = HttpClients.createDefault(); HttpPost httpPost = new HttpPost("https://example.com/oauth/token"); // 设置 grant_type 参数 List<NameValuePair> params = new ArrayList<NameValuePair>(); params.add(new BasicNameValuePair("grant_type", "password")); params.add(new BasicNameValuePair("username", "user")); params.add(new BasicNameValuePair("password", "password")); httpPost.setEntity(new UrlEncodedFormEntity(params)); CloseableHttpResponse response = httpclient.execute(httpPost); try { HttpEntity entity = response.getEntity(); String result = EntityUtils.toString(entity); System.out.println(result); } finally { response.close(); } } } ``` 以上示例代码中,`httpPost.setEntity(new UrlEncodedFormEntity(params))` 设置请求实体,将 `grant_type` 参数和其他参数放入请求实体中。 注意:以上示例代码仅供参考,具体实现还需要根据实际情况进行调整。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值