Spring Cloud OAuth2 (1) Building an authorization service

Overview

The content of this article is mainly for the construction of spring cloud authorization service, using jwt authentication.
GitHub address: https://github.com/fp2952/spring-cloud-base/tree/master/auth-center/auth-center-provider

add dependencies

Spring Security and the OAuth2 extension to Security

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>

Startup class annotation

Add @EnableAuthorizationServerannotation

@SpringCloudApplication
@EnableAuthorizationServer
@EnableFeignClients("com.peng.main.client")
public class AuthCenterProviderApplication {
   public static void main(String[] args){
       SpringApplication.run(AuthCenterProviderApplication.class, args);
   }
}

Oauth2 configuration class AuthorizationServerConfigurerAdapter

AuthorizationServerConfigurerAdapter中:

  • ClientDetailsServiceConfigurer: Used to configure the client details service (ClientDetailsService). The client details information is initialized here. You can write the client details information here or store and retrieve the details through the database.
  • AuthorizationServerSecurityConfigurer: used to configure the security constraints of the Token Endpoint.
  • AuthorizationServerEndpointsConfigurer: Used to configure access endpoints and token services for authorization and tokens.
    The main configuration is as follows:

Configure Client Details

ClientDetailsServiceConfigurer (a callback configuration item of AuthorizationServerConfigurer) can use memory or JDBC to implement client details service (ClientDetailsService). The configuration method of Spring Security OAuth2 is to write @Configuration class to inherit AuthorizationServerConfigurerAdapter , and then override void configure(ClientDetailsServiceConfigurer clients) method, such as :

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 使用JdbcClientDetailsService客户端详情服务
        clients.withClientDetails(new JdbcClientDetailsService(dataSource));
    }

Here, Jdbc is used to implement the client detail service. The data source dataSource is not described. The default table of the framework is used, and the schema link is:
https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security -oauth2/src/test/resources/schema.sql

Configuring Token Management (jwtAccessTokenConverter)

JwtAccessTokenConverter is a converter used to generate tokens, and token tokens are signed by default, and the resource server needs to verify this signature. There are two methods of encryption and signature verification here:
symmetric encryption and asymmetric encryption (public key key).
Symmetric encryption requires the authorization server and resource server to store the same key value, while asymmetric encryption can use key encryption to expose the public key To verify the signature of the resource server, asymmetric encryption is used in this article, which is configured in the AuthorizationServerConfigurerAdapter as follows:

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.authenticationManager(authenticationManager)
                // 配置JwtAccessToken转换器
                .accessTokenConverter(jwtAccessTokenConverter())
                // refresh_token需要userDetailsService
                .reuseRefreshTokens(false).userDetailsService(userDetailsService);
                //.tokenStore(getJdbcTokenStore());
    }

    /**
     * 使用非对称加密算法来对Token进行签名
     * @return
     */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {

        final JwtAccessTokenConverter converter = new JwtAccessToken();
        // 导入证书
        KeyStoreKeyFactory keyStoreKeyFactory =
                new KeyStoreKeyFactory(new ClassPathResource("keystore.jks"), "mypass".toCharArray());
        converter.setKeyPair(keyStoreKeyFactory.getKeyPair("mytest"));

        return converter;
    }

Generate the JKS certificate file through the JDK tool and put keystore.jks in the resource directory
keytool -genkeypair -alias mytest -keyalg RSA -keypass mypass -keystore keystore.jks -storepass mypass

Here we customize JwtAccessToken to add additional user information

/**
 * Created by fp295 on 2018/4/16.
 * 自定义JwtAccessToken转换器
 */
public class JwtAccessToken extends JwtAccessTokenConverter {

    /**
     * 生成token
     * @param accessToken
     * @param authentication
     * @return
     */
    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
        DefaultOAuth2AccessToken defaultOAuth2AccessToken = new DefaultOAuth2AccessToken(accessToken);

        // 设置额外用户信息
        BaseUser baseUser = ((BaseUserDetail) authentication.getPrincipal()).getBaseUser();
        baseUser.setPassword(null);
        // 将用户信息添加到token额外信息中
        defaultOAuth2AccessToken.getAdditionalInformation().put(Constant.USER_INFO, baseUser);

        return super.enhance(defaultOAuth2AccessToken, authentication);
    }

    /**
     * 解析token
     * @param value
     * @param map
     * @return
     */
    @Override
    public OAuth2AccessToken extractAccessToken(String value, Map<String, ?> map){
        OAuth2AccessToken oauth2AccessToken = super.extractAccessToken(value, map);
        convertData(oauth2AccessToken, oauth2AccessToken.getAdditionalInformation());
        return oauth2AccessToken;
    }

    private void convertData(OAuth2AccessToken accessToken,  Map<String, ?> map) {
        accessToken.getAdditionalInformation().put(Constant.USER_INFO,convertUserData(map.get(Constant.USER_INFO)));

    }

    private BaseUser convertUserData(Object map) {
        String json = JsonUtils.deserializer(map);
        BaseUser user = JsonUtils.serializable(json, BaseUser.class);
        return user;
    }
}

In the JwtAccessToken class, the user information is obtained from the getPrincipal (actually the UserDetails interface) in the authentication, so we need to implement our own UserDetails

/**
 * Created by fp295 on 2018/4/29.
 * 包装org.springframework.security.core.userdetails.User类
 */
public class BaseUserDetail implements UserDetails, CredentialsContainer {

    private final BaseUser baseUser;
    private final org.springframework.security.core.userdetails.User user;

    public BaseUserDetail(BaseUser baseUser, User user) {
        this.baseUser = baseUser;
        this.user = user;
    }


    @Override
    public void eraseCredentials() {
        user.eraseCredentials();
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return user.getAuthorities();
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getUsername();
    }

    @Override
    public boolean isAccountNonExpired() {
        return user.isAccountNonExpired();
    }

    @Override
    public boolean isAccountNonLocked() {
        return user.isAccountNonLocked();
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return user.isCredentialsNonExpired();
    }

    @Override
    public boolean isEnabled() {
        return user.isEnabled();
    }

    public BaseUser getBaseUser() {
        return baseUser;
    }
}

Authorization endpoint open

    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
        oauthServer
                // 开启/oauth/token_key验证端口无权限访问
                .tokenKeyAccess("permitAll()")
                // 开启/oauth/check_token验证端口认证权限访问
                .checkTokenAccess("isAuthenticated()");
    }

Security configuration

Need to configure DaoAuthenticationProvider, UserDetailService, etc.

@Configuration
@Order(ManagementServerProperties.ACCESS_OVERRIDE_ORDER)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    // 自动注入UserDetailsService
    @Autowired
    private BaseUserDetailService baseUserDetailService;

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http    // 配置登陆页/login并允许访问
                .formLogin().permitAll()
                // 登出页
                .and().logout().logoutUrl("/logout").logoutSuccessUrl("/")
                // 其余所有请求全部需要鉴权认证
                .and().authorizeRequests().anyRequest().authenticated()
                // 由于使用的是JWT,我们这里不需要csrf
                .and().csrf().disable();
    }

    /**
     * 用户验证
     * @param auth
     */
    @Override
    public void configure(AuthenticationManagerBuilder auth) {
        auth.authenticationProvider(daoAuthenticationProvider());
    }


    @Bean
    public DaoAuthenticationProvider daoAuthenticationProvider(){
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        // 设置userDetailsService
        provider.setUserDetailsService(baseUserDetailService);
        // 禁止隐藏用户未找到异常
        provider.setHideUserNotFoundExceptions(false);
        // 使用BCrypt进行密码的hash
        provider.setPasswordEncoder(new BCryptPasswordEncoder(6));
        return provider;
    }
}

UserDetailsService implementation

@Service
public class BaseUserDetailService implements UserDetailsService {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private BaseUserService baseUserService;
    @Autowired
    private BaseRoleService baseRoleService;
 
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        // 调用FeignClient查询用户
        ResponseData<BaseUser> baseUserResponseData = baseUserService.getUserByUserName(username);
        if(baseUserResponseData.getData() == null || !ResponseCode.SUCCESS.getCode().equals(baseUserResponseData.getCode())){
            logger.error("找不到该用户,用户名:" + username);
            throw new UsernameNotFoundException("找不到该用户,用户名:" + username);
        }
        BaseUser baseUser = baseUserResponseData.getData();

        // 调用FeignClient查询角色
        ResponseData<List<BaseRole>> baseRoleListResponseData = baseRoleService.getRoleByUserId(baseUser.getId());
        List<BaseRole> roles;
        if(baseRoleListResponseData.getData() == null ||  !ResponseCode.SUCCESS.getCode().equals(baseRoleListResponseData.getCode())){
            logger.error("查询角色失败!");
            roles = new ArrayList<>();
        }else {
            roles = baseRoleListResponseData.getData();
        }

        // 获取用户权限列表
        List<GrantedAuthority> authorities = new ArrayList();
        roles.forEach(e -> {
            // 存储用户、角色信息到GrantedAuthority,并放到GrantedAuthority列表
            GrantedAuthority authority = new SimpleGrantedAuthority(e.getRoleCode());
            authorities.add(authority);
        
        });

        // 返回带有用户权限信息的User
        org.springframework.security.core.userdetails.User user =  new org.springframework.security.core.userdetails.User(baseUser.getUserName(),
                baseUser.getPassword(), isActive(baseUser.getActive()), true, true, true, authorities);
        return new BaseUserDetail(baseUser, user);
    }

    private boolean isActive(int active){
        return active == 1 ? true : false;
    }
}

Authorization server verification

http://127.0.0.1:8080/oauth/authorize?client_id=clientId&response_type=code&redirect_uri=www.baidu.com

Note: client_id: client_id stored in the database, response_type: hard-coded code

  1. After linking to enter, enter the simple login page of spring security,
  2. Enter the account password, and click login for the user to be obtained by the implemented UserDetailsService.
  3. Enter the simple authorization page, click Authorize,
  4. Redirect to redirect_uri with code parameter:
    http://www.baidu.com?code=rTKETX
  5. post request to get token:

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325074194&siteId=291194637