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

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

GitHub address

Code cloud address

1. Preparation of project knowledge

  1. What is OAuth2

    The standard for OAuth 2.0 is the RFC 6749 document OAuth introduces an authorization layer that separates two different roles: client and resource owner. ...with the resource owner's consent, the resource server can issue a token to the client. The client passes the token to request data:

  2. OAuth2 Core - Authorization

  3. Four ways of OAuth2

model
Authorization-code
implicit (implicit)
password
client credentials

4. Handshake process (from RFC6749)

5. SpringSecurity

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

*6. The default built-in filter list in SpringSecurity:

Default built-in filter list in SpringSecurity*

(Other follow-up additions)

2. Project preparation

1. Environment

 SpringBoot 2.1.0.RELEASE jdk1.8   SpringCloud Greenwich.SR1  consul服务发现与注册 
  1. project
project describe
fp-commons jar public
fp-gateway gateway
fp-authorization-server OAuth2 Authentication Server
3. Project construction
(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 public

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

(3)fp-authorization-server

(A simplified version of the OAuth2 authentication center was submitted in git, with only the token generation function, no resource protection and authentication)

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

3. Build the authentication server

The basic SpringBoot construction is skipped, and only the token implementation of SpringSecurity OAuth2, basic data processing and other sources are introduced here.

First complete the custom implementation of ClientDetailsService (get client-related information)

/**
* @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;
    }
}

And a custom implementation of UserDetailsService (to get user-related information)

/**
 * @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;
    }
}

Then create a new authentication service configuration MySecurityOAuth2Config, inherit the 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;
    }
}

Then create a new Security configuration MySecurityConfig inherits 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());
    }
}

At this point, the SpringSecurity OAuth2 to Jane project is completed, and the next step is the basic verification of 4 tokens

4. 4 kinds of token acquisition (here, postman is used for token acquisition)

  1. password mode authentication

Request address:

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

parameter:

parameter name illustrate
grant_type Type must be password
username username
password password
client_id client id
client_heart client secret
ask:

Response: The returned information includes access_token pass token, refresh_token re-apply token, scope resource scope

verify:

Not authorized to access page

visit page

  1. client_credentials mode

Request address:

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

parameter:

parameter name illustrate
grant_type Type must be client_credentials
client_id client id
client_heart client secret
ask

Response: Return parameters include access_token, scope permission scope (but not refresh_token)

Verification: No permissions by default Of course, the permissions here can also be added by default in the authorities field in ClientDetails

  1. refresh_token mode

    refresh_token can only be re-applied for an available token with this value after the request has been made once and the refresh_token is obtained, and after applying for a new token, the original expired token will be invalid immediately, and the refresh_token will continue to be used as the refresh_token of the new token.

Request address:

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

parameter

parameter name illustrate
grant_type Type must be refresh_token
refresh_token refresh token

ask:

Response: The returned result includes the new token, the original refresh_token, and scope permissions

  1. authorization_code模式

ask:

第一次请求: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

parameter:

first parameter

parameter name illustrate
response_type type must be code
client_id client id
redirects The return code address corresponds to the redirect_uri of the back-end client_Id, and corresponds to the request token in the second step

second parameter

parameter name illustrate
grant_type Type must be authorization_code
code For the return code here is mbeBvY
redirects Return the code address to the previous redirect_uri http://192.168.3.14:8001/auth/oauth/token

Above, if you fill in the wrong one, an error will be reported. The code can only be used once and the request will fail again.

Jump to the login interface after the first request

After logging in, jump to the authorization page and then return to the redirect_uri path and bring the code

second request

Response: The returned result includes token, refresh_token, scope permission

This is the end of the 4 modes, and the simplified code of Spring Security OAuth2 ends here. Subsequent articles on custom response, url permission judgment, jwt form, etc. will be described in detail.

{{o.name}}
{{m.name}}

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=324130268&siteId=291194637