使用Security和Oauth2搭建授权服务器和资源服务器

安装

本文使用最新版本的依赖。

父pom,省略其他不相关依赖,根据实际指定和添加

<dependencyManagement>
        <dependencies>
            <!-- spring-cloud -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>2020.0.3</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!-- spring-cloud-alibaba -->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2021.1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!-- spring boot -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-parent</artifactId>
                <version>2.4.6</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

uaa工程,即认证服务

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

发现,依赖没被导入,查阅资料发现2020版本不再提供版本,需要自己添加

<!-- oauth2 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
    <version>2.2.5.RELEASE</version>
</dependency>
<!-- security -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-security</artifactId>
    <version>2.2.5.RELEASE</version>
</dependency>

授权服务器

授权服务器3个重点

  1. 令牌管理服务
  2. 令牌安全约束
  3. 令牌端点服务

完整代码如下,MyUserDetailsService需要自己定义

/**
 * 认证服务
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    final AuthenticationManager authenticationManager;
    final JwtAccessTokenConverter jwtAccessTokenConverter;
    final TokenStore tokenStore;
    final MyUserDetailsService myUserDetailsService;
    final DataSource dataSource;
    final PasswordEncoder passwordEncoder;
    @Resource
    AuthorizationCodeServices authorizationCodeServices;
    @Resource
    ClientDetailsService clientDetails;

    public AuthorizationServerConfig(AuthenticationManager authenticationManager,
                                     JwtAccessTokenConverter jwtAccessTokenConverter,
                                     TokenStore tokenStore,
                                     MyUserDetailsService myUserDetailsService,
                                     DataSource dataSource,
                                     PasswordEncoder passwordEncoder) {
        this.authenticationManager = authenticationManager;
        this.jwtAccessTokenConverter = jwtAccessTokenConverter;
        this.tokenStore = tokenStore;
        this.myUserDetailsService = myUserDetailsService;
        this.dataSource = dataSource;
        this.passwordEncoder = passwordEncoder;
    }

    /**
     * 将客户端信息存储到数据库
     *
     * @return
     */
    @Bean
    public ClientDetailsService clientDetails() {
        JdbcClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);
        clientDetailsService.setPasswordEncoder(passwordEncoder);
        return clientDetailsService;
    }

    /**
     * 令牌管理服务
     *
     * @return
     */
    @Bean
    public AuthorizationServerTokenServices tokenService() {
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setClientDetailsService(clientDetails);//客户端详情服务
        defaultTokenServices.setSupportRefreshToken(true);//不支持刷新令牌
        defaultTokenServices.setTokenStore(tokenStore);//令牌存储策略
        defaultTokenServices.setAccessTokenValiditySeconds(7200);
        defaultTokenServices.setRefreshTokenValiditySeconds(259200);
        //令牌增强
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Collections.singletonList(jwtAccessTokenConverter));
        defaultTokenServices.setTokenEnhancer(tokenEnhancerChain);
        return defaultTokenServices;
    }

    /**
     * 设置授权码模式
     *
     * @return
     */
    @Bean
    public AuthorizationCodeServices authorizationCodeServices() {
        return new JdbcAuthorizationCodeServices(dataSource);
    }

    /**
     * 令牌安全约束
     *
     * @param security
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) {
        security.tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()")
                .allowFormAuthenticationForClients();
    }

    /**
     * 客户端详情服务
     *
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(clientDetails);
    }

    /**
     * 令牌端点服务
     *
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager)//密码模式
                .authorizationCodeServices(authorizationCodeServices)//授权码服务
                .tokenServices(tokenService())//令牌管理服务
                .reuseRefreshTokens(false).userDetailsService(myUserDetailsService)//刷新令牌
                .allowedTokenEndpointRequestMethods(HttpMethod.POST);
    }
}

CustomAccessDeniedHandler

/**
 * 自定义 Access Denied
 */
public class CustomAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException {
        httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
        httpServletResponse.setCharacterEncoding(String.valueOf(StandardCharsets.UTF_8));
        httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
        PrintWriter printWriter = httpServletResponse.getWriter();
        printWriter.print(new ObjectMapper().writeValueAsString(HttpResult.failed(ConstVal.ACCESS_DENIED_MSG)));
        printWriter.flush();
        printWriter.close();
    }
}

CustomAuthenticationEntryPoint

public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException {
        httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        httpServletResponse.setCharacterEncoding(String.valueOf(StandardCharsets.UTF_8));
        httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);

        PrintWriter printWriter = httpServletResponse.getWriter();

        Throwable cause = e.getCause();
        if (cause instanceof InvalidTokenException) {
            printWriter.print(new ObjectMapper().writeValueAsString(HttpResult.failed(ConstVal.INVALID_TOKEN_MSG)));
        } else {
            printWriter.print(new ObjectMapper().writeValueAsString(HttpResult.failed(ConstVal.UNAUTHORIZED_MSG)));
        }
        printWriter.flush();
        printWriter.close();
    }
}

TokenConfig

@Configuration
public class TokenConfig {

    @Resource(name = "keyProp")
    private KeyProperties keyProperties;

    @Bean("keyProp")
    public KeyProperties keyProperties() {
        return new KeyProperties();
    }

    /**
     * 使用redis存储认证token
     *
     * @return
     */
    @Bean
    public TokenStore tokenStore() {
        //无状态方式的存储
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    /**
     * 使用非对称加密算法来对Token进行签名
     *
     * @return
     */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        KeyPair keyPair = (new KeyStoreKeyFactory(this.keyProperties.getKeyStore().getLocation(), this.keyProperties.getKeyStore().getSecret().toCharArray()))
                .getKeyPair(this.keyProperties.getKeyStore().getAlias(), this.keyProperties.getKeyStore().getPassword().toCharArray());
        converter.setKeyPair(keyPair);
        return converter;
    }
}

WebSecurityConfig

@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    final MyUserDetailsService userDetailsService;

    public WebSecurityConfig(MyUserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    /**
     * 认证管理器
     *
     * @return
     * @throws Exception
     */
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * 密码编码器
     *
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 用户信息服务
     *
     * @return
     */
    @Bean
    public DaoAuthenticationProvider daoAuthenticationProvider() {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setUserDetailsService(userDetailsService);
        provider.setHideUserNotFoundExceptions(false);
        provider.setPasswordEncoder(passwordEncoder());
        return provider;
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        super.configure(web);
    }

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

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests().antMatchers("/oauth/token").permitAll().anyRequest().authenticated()
                .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and().exceptionHandling().accessDeniedHandler(new CustomAccessDeniedHandler())//自定义异常处理
                .authenticationEntryPoint(new CustomAuthenticationEntryPoint())
                .and().formLogin();
    }
}
encrypt:
  key-store:
    location: classpath:keystore.jks
    secret: ice
    alias: ice
    password: ice
keytool -genkeypair -alias ice -keyalg RSA -keypass ice -keystore keystore.jks -storepass ice

资源服务器

资源服务器比较简单,添加依赖和配置文件即可

WebSecurityConfig配置文件

@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable().authorizeRequests().anyRequest().authenticated()
                .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }
}
@Configuration
@EnableResourceServer
@Slf4j
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @SneakyThrows
    @Bean
    public ResourceServerTokenServices tokenService() {
        log.info(">>> 资源服务器认证...");
        DefaultTokenServices service = new DefaultTokenServices();

        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setVerifierKey(new String(FileCopyUtils.copyToByteArray(
                new ClassPathResource("public.txt").getInputStream())));
        converter.afterPropertiesSet();
        service.setTokenStore(new JwtTokenStore(converter));
        return service;
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources.resourceId("ice_cloud_life").tokenServices(tokenService());
    }
}

以上需要使用公钥文件

keytool -importkeystore -srckeystore keystore.jks -destkeystore keystore.jks -deststoretype pkcs12

一开始,没有生效,是因为没写converter.afterPropertiesSet();

调试代码JwtAccessTokenConverter.java

protected Map<String, Object> decode(String token) {
	try {
		Jwt jwt = JwtHelper.decodeAndVerify(token, verifier);
		String claimsStr = jwt.getClaims();
		Map<String, Object> claims = objectMapper.parseMap(claimsStr);
		if (claims.containsKey(EXP) && claims.get(EXP) instanceof Integer) {
			Integer intValue = (Integer) claims.get(EXP);
			claims.put(EXP, new Long(intValue));
		}
		this.getJwtClaimsSetVerifier().verify(claims);
		return claims;
	}
	catch (Exception e) {
		throw new InvalidTokenException("Cannot convert access token to JSON", e);
	}
}

第一句就报错了,因为verifier未null。通过在类中寻找verifier的初始化后发现。

	public void afterPropertiesSet() throws Exception {
		if (verifier != null) {
			// Assume signer also set independently if needed
			return;
		}
		//....
	}

于是在配置文件中添加调用。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值