Oauth2---AuthorizationServer配置

       AuthorizationServerConfigurerAdapter只是一个提供给开发配置ClientDetailsServiceConfigurer、AuthorizationServerEndpointsConfigurer、AuthorizationServerSecurityConfigurer空壳类并没有持有以上三个配置Bean对象。由初始化时调用AuthorizationServerConfigurerAdapter.configure(xxxConfigure) 给机会开发注入、配置。

开发定义的配置列表:

1. ClientDetailsService:ClientDetails信息加载实现类。

2. TokenStore:token管理服务。

3. TokenEnhancer:token信息的额外信息处理。

4. PasswordEncoder: clientDetail 信息里的client_secret字段加解密器。

一、核心类:

1. AuthorizationServerConfigurerAdapter: AuthorizationServer配置的适配类。

2. OAuth2AuthorizationServerConfiguration:系统提供的配置实现类,用户自定时可参考这类的实现。

但这对象生成条件:

//1.没有其它AuthorizationServerConfigurer的配置对象,如果用户自己有定义就不产生。
//2.yml配置文件有:security.oauth2.authorization属性配置。@ConditionalOnMissingBean(AuthorizationServerConfigurer.class)
@EnableConfigurationProperties(AuthorizationServerProperties.class)

3. 开发自定义MyAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter:

完成相关的配置(向这个些类注入自定义的Bean):

(1)ClientDetailsServiceConfigurer:clientDetailsService注入,决定clientDeatils信息的处理服务。
(2)AuthorizationServerEndpointsConfigurer:访问端点配置。tokenStroe、tokenEnhancer服务
(3)AuthorizationServerSecurityConfigurer:访问安全配置。

 

二、配置类:

1. ClientDetailsServiceConfigurer:

      主要是注入ClientDetailsService实例对象(唯一配置注入)。其它地方可以通过ClientDetailsServiceConfigurer调用开发配置的ClientDetailsService。系统提供的二个ClientDetailsService实现类:JdbcClientDetailsService、InMemoryClientDetailsService。

回调配置ClientDetailsServiceConfigurer

   /**
	 *
	 * 配置从哪里获取ClientDetails信息。
	 * 在client_credentials授权方式下,只要这个ClientDetails信息。
	 * @param clientsDetails
	 * @throws Exception
	 */
	@Override
	public void configure(ClientDetailsServiceConfigurer clientsDetails) throws Exception {
		//认证信息从数据库获取
		clientsDetails.withClientDetails(clientDetailsService);
		// 测试用,将客户端信息存储在内存中
		clientsDetails.inMemory()
				.withClient("client")   // client_id
				.secret("secret")       // client_secret
				.authorizedGrantTypes("authorization_code")     // 该client允许的授权类型
				.scopes("app")     // 允许的授权范围
				.autoApprove(true); //登录后绕过批准询问(/oauth/confirm_access)
			
	}

JdbcClientDetailsService类 


public class JdbcClientDetailsService implements ClientDetailsService, ClientRegistrationService {

    //操作oauth_client_details数据库SQL语句,另外可以自行注入
    private static final String CLIENT_FIELDS_FOR_UPDATE = "resource_ids, scope, "
            + "authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, "
            + "refresh_token_validity, additional_information, autoapprove";
    private static final String CLIENT_FIELDS = "client_secret, " + CLIENT_FIELDS_FOR_UPDATE;
    private static final String BASE_FIND_STATEMENT = "select client_id, " + CLIENT_FIELDS + " from oauth_client_details";
    private static final String DEFAULT_FIND_STATEMENT = BASE_FIND_STATEMENT + " order by client_id";
    private static final String DEFAULT_SELECT_STATEMENT = BASE_FIND_STATEMENT + " where client_id = ?";
    private static final String DEFAULT_INSERT_STATEMENT = "insert into oauth_client_details (" + CLIENT_FIELDS
            + ", client_id) values (?,?,?,?,?,?,?,?,?,?,?)";
    private static final String DEFAULT_UPDATE_STATEMENT = "update oauth_client_details " + "set "
            + CLIENT_FIELDS_FOR_UPDATE.replaceAll(", ", "=?, ") + "=? where client_id = ?";
    private static final String DEFAULT_UPDATE_SECRET_STATEMENT = "update oauth_client_details "
            + "set client_secret = ? where client_id = ?";
    private static final String DEFAULT_DELETE_STATEMENT = "delete from oauth_client_details where client_id = ?";

    private RowMapper<ClientDetails> rowMapper = new ClientDetailsRowMapper();
    //1.用于client_secret密码入库与出库时转化
    private PasswordEncoder passwordEncoder = NoOpPasswordEncoder.getInstance();
    //2.数据库存操作。
    private final JdbcTemplate jdbcTemplate;
    private JdbcListFactory listFactory;

    public JdbcClientDetailsService(DataSource dataSource) {
        Assert.notNull(dataSource, "DataSource required");
        this.jdbcTemplate = new JdbcTemplate(dataSource);
        this.listFactory = new DefaultJdbcListFactory(new NamedParameterJdbcTemplate(jdbcTemplate));
    }

    /**
     * 核心方法。加载ClientDetails by clientId
     */
    public ClientDetails loadClientByClientId(String clientId) throws InvalidClientException {
        ClientDetails details;
        try {
            details = jdbcTemplate.queryForObject(selectClientDetailsSql, new ClientDetailsRowMapper(), clientId);
        }
        catch (EmptyResultDataAccessException e) {
            throw new NoSuchClientException("No client with requested id: " + clientId);
        }
        return details;
    }

}
InMemoryClientDetailsService类
public class InMemoryClientDetailsService implements ClientDetailsService {

  private Map<String, ClientDetails> clientDetailsStore = new HashMap<String, ClientDetails>();

  public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException {
    ClientDetails details = clientDetailsStore.get(clientId);
    return details;
  }
}

2. AuthorizationServerEndpointsConfigurer端点配置

AuthorizationServerEndpointsConfigurer其实是一个装载类,装载Endpoints所有相关的类配置(AuthorizationServer、TokenServices、TokenStore、ClientDetailsService、UserDetailsService)。

配置方法:

    /**
	 * 注入相关配置:
	 * 1. 密码模式下配置认证管理器 AuthenticationManager
	 * 2. 设置 AccessToken的存储介质tokenStore, 默认使用内存当做存储介质。
      * 3. userDetailsService注入
	 */
	@Override
	public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {

		TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
		endpoints
				.authenticationManager(authenticationManager)
				//末确认点.userDetailsService(userDetailsService) //MUST:密码模式下需设置一个AuthenticationManager对象,获取 UserDetails信息
				.tokenStore(tokenStore)//token的保存方式
				.tokenEnhancer(tokenEnhancerChain);//token里加点信息
}

AuthorizationServerEndpointsConfigurer类: 

public final class AuthorizationServerEndpointsConfigurer {

	private AuthorizationServerTokenServices tokenServices;
	private ConsumerTokenServices consumerTokenServices;
	private AuthorizationCodeServices authorizationCodeServices;
	private ResourceServerTokenServices resourceTokenServices;
	private TokenStore tokenStore;
	private TokenEnhancer tokenEnhancer;
	private AccessTokenConverter accessTokenConverter;
	private ApprovalStore approvalStore;
	private TokenGranter tokenGranter;
	private OAuth2RequestFactory requestFactory;
	private OAuth2RequestValidator requestValidator;
	private UserApprovalHandler userApprovalHandler;
	private AuthenticationManager authenticationManager;
	private ClientDetailsService clientDetailsService;
    private String prefix;
	private Map<String, String> patternMap = new HashMap<String, String>();
	private Set<HttpMethod> allowedTokenEndpointRequestMethods = new HashSet<HttpMethod>();

	private FrameworkEndpointHandlerMapping frameworkEndpointHandlerMapping;

	private boolean approvalStoreDisabled;
	private List<Object> interceptors = new ArrayList<Object>();

	private DefaultTokenServices defaultTokenServices;

	private UserDetailsService userDetailsService;

	private boolean tokenServicesOverride = false;

	private boolean userDetailsServiceOverride = false;

	private boolean reuseRefreshToken = true;

	private WebResponseExceptionTranslator<OAuth2Exception> exceptionTranslator;

	private RedirectResolver redirectResolver;

/**
* tokenServices重新生成一个PreAuthenticatedAuthenticationProvider作为认证。
*/
private void addUserDetailsService(DefaultTokenServices tokenServices, UserDetailsService userDetailsService) {
		if (userDetailsService != null) {
			PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
			provider.setPreAuthenticatedUserDetailsService(new UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken>(
					userDetailsService));
			tokenServices
					.setAuthenticationManager(new ProviderManager(Arrays.<AuthenticationProvider> asList(provider)));
		}
	}
}

三、AuthorizationServerSecurityConfigurer端点安全配置:

  AuthorizationServerSecurityConfigurer继承SecurityConfigurerAdapter.也就是一个 Spring Security安全配置提供给AuthorizationServer去配置AuthorizationServer的端点(/oauth/****)的安全访问规则、过滤器Filter。

类继承关系:

可配置属性项: 

1. ClientDetail加密方式

2. allowFormAuthenticationForClients 允许表单认证。针对/oauth/token端点。

3. 添加开发配置tokenEndpointAuthenticationFilters

4. tokenKeyAccess、checkTokenAccess访问权限。

 

/**
	 *  配置:安全检查流程,用来配置令牌端点(Token Endpoint)的安全与权限访问
	 *  默认过滤器:BasicAuthenticationFilter
	 *  1、oauth_client_details表中clientSecret字段加密【ClientDetails属性secret】
	 *  2、CheckEndpoint类的接口 oauth/check_token 无需经过过滤器过滤,默认值:denyAll()
	 * 对以下的几个端点进行权限配置:
	 * /oauth/authorize:授权端点
	 * /oauth/token:令牌端点
	 * /oauth/confirm_access:用户确认授权提交端点
	 * /oauth/error:授权服务错误信息端点
	 * /oauth/check_token:用于资源服务访问的令牌解析端点
	 * /oauth/token_key:提供公有密匙的端点,如果使用JWT令牌的话
	 **/
	@Override
	public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
		security.allowFormAuthenticationForClients()//允许客户表单认证
				.passwordEncoder(new BCryptPasswordEncoder())//设置oauth_client_details中的密码编码器
				.tokenKeyAccess("permitAll()")
				.checkTokenAccess("isAuthenticated()")
				.passwordEncoder(oauthClientPasswordEncoder);
	}

AuthorizationServerSecurityConfigurer类

public final class AuthorizationServerSecurityConfigurer extends
        SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {

    private AuthenticationEntryPoint authenticationEntryPoint;
    private AccessDeniedHandler accessDeniedHandler = new OAuth2AccessDeniedHandler();
    //client secrets加密器
    private PasswordEncoder passwordEncoder;
    private String realm = "oauth2/client";
    private boolean allowFormAuthenticationForClients = false;
    private String tokenKeyAccess = "denyAll()";
    private String checkTokenAccess = "denyAll()";
    private boolean sslOnly = false;
    //开发定义过滤器
    private List<Filter> tokenEndpointAuthenticationFilters = new ArrayList<Filter>();


    @Override
    public void init(HttpSecurity http) throws Exception {
        //1.异常发生时的入口配置
        registerDefaultAuthenticationEntryPoint(http);
        //1. passwordEncoder注入到ClientDetailsUserDetailsService(
        // UserDetailsService对象存储在HttpSecurity.SharedObject里。
        if (passwordEncoder != null) {
            ClientDetailsUserDetailsService clientDetailsUserDetailsService = new ClientDetailsUserDetailsService(clientDetailsService());
            clientDetailsUserDetailsService.setPasswordEncoder(passwordEncoder());
            http.getSharedObject(AuthenticationManagerBuilder.class)
                    .userDetailsService(clientDetailsUserDetailsService)
                    .passwordEncoder(passwordEncoder());
        }
        else {
            http.userDetailsService(new ClientDetailsUserDetailsService(clientDetailsService()));
        }
        //2.配置/oaut/***端点 httpBasic安全规则
        http.securityContext().securityContextRepository(new NullSecurityContextRepository()).and().csrf().disable()
                .httpBasic().realmName(realm);
        //3. ssl 通道安全
        if (sslOnly) {
            http.requiresChannel().anyRequest().requiresSecure();
        }
    }

    /**
     * 开发配置:
     * 1. allowFormAuthenticationForClients 允许表单认证。针对/oauth/token端点添加ClientCredentialsTokenEndpointFilter
     * 2. 添加开发配置tokenEndpointAuthenticationFilters。(在 BasicAuthenticationFilter之前)
     * 3. 添加在 accessDeniedHandler
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {

        // ensure this is initialized
        frameworkEndpointHandlerMapping();
        //2. 针对/oauth/token端点添加ClientCredentialsTokenEndpointFilter
        if (allowFormAuthenticationForClients) {
            clientCredentialsTokenEndpointFilter(http);
        }
        //3.添加开发配置tokenEndpointAuthenticationFilters。(在 BasicAuthenticationFilter之前)
        for (Filter filter : tokenEndpointAuthenticationFilters) {
            http.addFilterBefore(filter, BasicAuthenticationFilter.class);
        }
        //4. 添加在 accessDeniedHandler,处理访问AccessDeniedException发生时处理。
        http.exceptionHandling().accessDeniedHandler(accessDeniedHandler);
    }

    /**
     * 配置异常发生时入口点AuthenticationEntryPoint
     * @param http
     */
    private void registerDefaultAuthenticationEntryPoint(HttpSecurity http) {
        ExceptionHandlingConfigurer<HttpSecurity> exceptionHandling = http.getConfigurer(ExceptionHandlingConfigurer.class);
        if (exceptionHandling == null) {
            return;
        }
        //1.端点入口:BasicAuthenticationEntryPoint设置response配置。
        // response.addHeader("WWW-Authenticate", "Basic realm=\"" + realmName + "\"");
        // response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
        if (authenticationEntryPoint==null) {
            BasicAuthenticationEntryPoint basicEntryPoint = new BasicAuthenticationEntryPoint();
            basicEntryPoint.setRealmName(realm);
            authenticationEntryPoint = basicEntryPoint;
        }
        //发生异常时,会调用到BasicAuthenticationEntryPoint.commence()
        exceptionHandling.defaultAuthenticationEntryPointFor(postProcess(authenticationEntryPoint), preferredMatcher);
    }

    /**
     * 核心:
     * "/oauth/token"端点的添加过滤器clientCredentialsTokenEndpointFilter(clientId, client_secert)密码比对。而且
     *  放到BasicAuthenticationFilter之前。
     *  http.addFilterBefore(clientCredentialsTokenEndpointFilter, BasicAuthenticationFilter.class);
     */
    private ClientCredentialsTokenEndpointFilter clientCredentialsTokenEndpointFilter(HttpSecurity http) {
        ClientCredentialsTokenEndpointFilter clientCredentialsTokenEndpointFilter = new ClientCredentialsTokenEndpointFilter(
                frameworkEndpointHandlerMapping().getServletPath("/oauth/token"));
        clientCredentialsTokenEndpointFilter
                .setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
        OAuth2AuthenticationEntryPoint authenticationEntryPoint = new OAuth2AuthenticationEntryPoint();
        authenticationEntryPoint.setTypeName("Form");
        authenticationEntryPoint.setRealmName(realm);
        clientCredentialsTokenEndpointFilter.setAuthenticationEntryPoint(authenticationEntryPoint);
        clientCredentialsTokenEndpointFilter = postProcess(clientCredentialsTokenEndpointFilter);
        http.addFilterBefore(clientCredentialsTokenEndpointFilter, BasicAuthenticationFilter.class);
        return clientCredentialsTokenEndpointFilter;
    }

    /**
     * 居然使用共享方式
     */
    private ClientDetailsService clientDetailsService() {
        return getBuilder().getSharedObject(ClientDetailsService.class);
    }

    private FrameworkEndpointHandlerMapping frameworkEndpointHandlerMapping() {
        return getBuilder().getSharedObject(FrameworkEndpointHandlerMapping.class);
    }

    private PasswordEncoder passwordEncoder() {
        return new PasswordEncoder() {
            @Override
            public boolean matches(CharSequence rawPassword, String encodedPassword) {
                return StringUtils.hasText(encodedPassword) ? passwordEncoder.matches(rawPassword, encodedPassword)
                        : true;
            }
            @Override
            public String encode(CharSequence rawPassword) {
                return passwordEncoder.encode(rawPassword);
            }
        };
    }

}
public class ClientCredentialsTokenEndpointFilter extends AbstractAuthenticationProcessingFilter {

	private AuthenticationEntryPoint authenticationEntryPoint = new OAuth2AuthenticationEntryPoint();

    public ClientCredentialsTokenEndpointFilter() {
		this("/oauth/token");
	}

	public ClientCredentialsTokenEndpointFilter(String path) {
		super(path);
		setRequiresAuthenticationRequestMatcher(new ClientCredentialsRequestMatcher(path));
		// If authentication fails the type is "Form"
		((OAuth2AuthenticationEntryPoint) authenticationEntryPoint).setTypeName("Form");
	}

	@Override
	public void afterPropertiesSet() {
		super.afterPropertiesSet();
		setAuthenticationFailureHandler(new AuthenticationFailureHandler() {
			public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
					AuthenticationException exception) throws IOException, ServletException {
				if (exception instanceof BadCredentialsException) {
					exception = new BadCredentialsException(exception.getMessage(), new BadClientCredentialsException());
				}
				authenticationEntryPoint.commence(request, response, exception);
			}
		});
		setAuthenticationSuccessHandler(new AuthenticationSuccessHandler() {
			public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
					Authentication authentication) throws IOException, ServletException {
				// no-op - just allow filter chain to continue to token endpoint
			}
		});
	}

	@Override
	public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
			throws AuthenticationException, IOException, ServletException {
        //1.获取请求参数:clientId、clientSecret用来认证
		String clientId = request.getParameter("client_id");
		String clientSecret = request.getParameter("client_secret");

		//2.如果认证过就不需要再认证
		Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
		if (authentication != null && authentication.isAuthenticated()) {
			return authentication;
		}
		if (clientId == null) {
			throw new BadCredentialsException("No client credentials presented");
		}
		if (clientSecret == null) {
			clientSecret = "";
		}

		UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(clientId,
				clientSecret);
        //4.开始认证
		return this.getAuthenticationManager().authenticate(authRequest);

	}

 

  • 21
    点赞
  • 64
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
您好,针对 `spring-boot-starter-oauth2-authorization-server` 版本为 1.1.0 的配置,以下是一个简单的示例: 1. 首先,在 `pom.xml` 文件中添加以下依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-authorization-server</artifactId> <version>1.1.0</version> </dependency> ``` 2. 然后,在 `application.yml` 文件中添加以下配置: ```yaml spring: security: oauth2: authorization: server: jwt: key-value: "secret" client: registration: myapp: client-id: myapp client-secret: secret scope: read,write redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}" authorization-grant-type: authorization_code client-name: My App provider: myapp: issuer-uri: http://localhost:8080/oauth2/token authorization-uri: http://localhost:8080/oauth2/authorize token-uri: http://localhost:8080/oauth2/token user-info-uri: http://localhost:8080/userinfo user-name-attribute: sub ``` 在上述配置中,`jwt.key-value` 用于指定加密 JWT 的密钥,`client.registration.myapp` 用于指定客户端信息,`provider.myapp` 用于指定 OAuth2 服务提供商信息。 3. 最后,在启动类中添加 `@EnableAuthorizationServer` 注解即可启用 OAuth2 授权服务器功能: ```java @SpringBootApplication @EnableAuthorizationServer public class AuthorizationServerApplication { public static void main(String[] args) { SpringApplication.run(AuthorizationServerApplication.class, args); } } ``` 希望以上内容能够帮助到您。如果您有任何疑问,请随时提出。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值