文章目录
概述
在配置Oauth2授权服务时,需要配置,一般是自定义子类,实现AuthorizationServerConfigurerAdapter
接口。本篇介绍这个接口的用法。
1. AuthorizationServerConfigurerAdapter
package org.springframework.security.oauth2.config.annotation.web.configuration;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
/**
* @author Dave Syer
*
*/
public class AuthorizationServerConfigurerAdapter implements AuthorizationServerConfigurer {
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
}
}
AuthorizationServerConfigurerAdapter中存在3个抽象方法:
-
AuthorizationServerSecurityConfigurer:用来配置令牌端点(Token Endpoint)的安全约束.
-
ClientDetailsServiceConfigurer:用来配置客户端详情服务(ClientDetailsService),客户端详情信息在这里进行初始化,你能够把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息。
clientId是什么?假设登录京东时,借助微信登录,那么提供授权服务就是微信,微信拥有oauth_client_details表,然后京东提前在微信注册了一个clientId,京东使用这个id来向微信表明身份,这样,微信就知道是京东来访问自己了。
-
AuthorizationServerEndpointsConfigurer:用来配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)。
2. ClientDetailsServiceConfigurer
ClientDetailsServiceConfigurer 主要是注入ClientDetailsService
实例对象(AuthorizationServerConfigurer 的一个回调配置项,唯一配置注入) ,能够使用内存或者JDBC来实现客户端详情服务(ClientDetailsService),系统提供的两个ClientDetailsService实现类:JdbcClientDetailsService
、InMemoryClientDetailsService
。
Spring Security OAuth2的配置方法是编写@Configuration类继承AuthorizationServerConfigurerAdapter,然后重写void configure(ClientDetailsServiceConfigurer clients)方法,如数据库形式:
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 使用JdbcClientDetailsService客户端详情服务
clients.withClientDetails(new JdbcClientDetailsService(dataSource));
}
这里使用Jdbc实现客户端详情服务,数据源dataSource不做叙述,使用框架默认的表,schema链接:
https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql
内存形式:
/**
*
* 配置从哪里获取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)
}
1.1 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;
}
}
1.2 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;
}
}
3. AuthorizationServerEndpointsConfigurer端点配置
AuthorizationServerEndpointsConfigurer
其实是一个装载类,装载Endpoints所有相关的类配置(AuthorizationServer、TokenServices
、TokenStore
、ClientDetailsService、UserDetailsService
)。
也就是说进行密码验证的一些工具类或服务类,均在这个地方进行注入,例如UserDetailsService,我们知道UserDetailsService是负责从数据库读取用户数据的,用户数据包含密码信息,这样,框架就可以判断前端传入的用户名和密码是否正确。
如果启用token校验的话,就需要注入TokenServices,TokenStore是对token的额外补充,用来确定token存储的。
因此,不是所有的参数都是必须的,需要根据实际场景来确定使用的参数。
用法举例如下:
@Autowired
private AuthenticationManager authenticationManager;
/**
* 注入相关配置:
* 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)));
}
}
}
3.1 配置令牌 管理 (jwtAccessTokenConverter)
JwtAccessTokenConverter是用来生成token的转换器,而token令牌默认是有签名的,且资源服务器需要验证这个签名。此处的加密及验签包括两种方式:
- 对称加密
- 非对称加密(公钥密钥)
对称加密需要授权服务器和资源服务器存储同一key值,而非对称加密可使用密钥加密,暴露公钥给资源服务器验签,本文中使用非对称加密方式,配置于AuthorizationServerConfigurerAdapter如下:
@Autowired
private AuthenticationManager authenticationManager;
@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 MyJwtAccessTokenConverter ();
// 导入证书
KeyStoreKeyFactory keyStoreKeyFactory =
new KeyStoreKeyFactory(new ClassPathResource("keystore.jks"), "mypass".toCharArray());
converter.setKeyPair(keyStoreKeyFactory.getKeyPair("mytest"));
return converter;
}
通过 JDK 工具生成 JKS 证书文件,并将 keystore.jks 放入resource目录下:
keytool -genkeypair -alias mytest -keyalg RSA -keypass mypass -keystore keystore.jks -storepass mypass
此处我们自定义JwtAccessToken用于添加额外用户信息:
当然,如果不需要额外信息的话,直接使用new JwtAccessTokenConverter ()就可以了
/**
* Created by fp295 on 2018/4/16.
* 自定义JwtAccessToken转换器
*/
public class MyJwtAccessTokenConverter 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;
}
}
MyJwtAccessTokenConverter
类中从authentication里的getPrincipal()
(实际为UserDetails接口)获取用户信息,所以我们需要实现自己的UserDetails:
核心接口是UserDetails,CredentialsContainer 是非必须的
/**
* 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;
}
}
3.2 AuthenticationManager
AuthenticationManager
是一个用来处理认证(Authentication)请求的接口。在其中只定义了一个方法authenticate()
,该方法只接收一个代表认证请求的Authentication对象作为参数,如果认证成功,则会返回一个封装了当前用户权限等信息的Authentication对象进行返回。
Authentication authenticate(Authentication authentication) throws AuthenticationException;
在Spring Security中,AuthenticationManager的默认实现是ProviderManager
,而且它不直接自己处理认证请求,而是委托给其所配置的AuthenticationProvider列表
,然后会依次使用每一个AuthenticationProvider进行认证:
-
如果有一个AuthenticationProvider认证后的结果不为null,则表示该AuthenticationProvider已经认证成功,之后的AuthenticationProvider将不再继续认证。然后直接以该AuthenticationProvider的认证结果作为ProviderManager的认证结果。
-
如果所有的AuthenticationProvider的认证结果都为null,则表示认证失败,将抛出一个ProviderNotFoundException。
校验认证请求最常用的方法是根据请求的用户名加载对应的UserDetails
,然后比对UserDetails的密码与认证请求的密码是否一致,一致则表示认证通过。
Spring Security内部的DaoAuthenticationProvider
就是使用的这种方式。其内部使用UserDetailsService
来负责加载UserDetails
。在认证成功以后会使用加载的UserDetails来封装要返回的Authentication对象,加载的UserDetails对象是包含用户权限等信息的。认证成功返回的Authentication对象将会保存在当前的SecurityContext中。
3.2.1 UserDetailsService
从前文得知,DaoAuthenticationProvider实现了密码比较的逻辑,比较的是前端传入的用户名和密码以及存在数据库中的用户名和密码。
当然,用户名和密码也可以存在内存中
那么数据库中的密码是如何得到的呢?就是借助UserDetailsService实现的。
UserDetailsService只定义了一个方法 loadUserByUsername()
,根据用户名可以查到用户并返回的方法。
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
logger.debug("权限框架-加载用户");
List<GrantedAuthority> auths = new ArrayList<>();
BaseUser baseUser = new BaseUser();
baseUser.setUserName(username);
baseUser = baseUserService.selectOne(baseUser);
if (baseUser == null) {
logger.debug("找不到该用户 用户名:{}", username);
throw new UsernameNotFoundException("找不到该用户!");
}
if(baseUser.getStatus()==2)
{
logger.debug("用户被禁用,无法登陆 用户名:{}", username);
throw new UsernameNotFoundException("用户被禁用!");
}
List<BaseRole> roles = baseRoleService.selectRolesByUserId(baseUser.getId());
if (roles != null) {
//设置角色名称
for (BaseRole role : roles) {
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(role.getRoleCode());
auths.add(authority);
}
}
return new org.springframework.security.core.userdetails.User(baseUser.getUserName(), baseUser.getUserPassword(), true, true, true, true, auths);
}
loadUserByUsername()
的实现可以是内存,可以是数据库,也可以是通过rpc从另一个微服务中得到。
3.3 AuthorizationServerSecurityConfigurer端点安全配置
AuthorizationServerSecurityConfigurer
继承SecurityConfigurerAdapter
,也就是一个 Spring Security安全配置提供给AuthorizationServer去配置AuthorizationServer的端点(/oauth/****)的安全访问规则、过滤器Filter。
可配置属性项:
-
ClientDetail加密方式
-
allowFormAuthenticationForClients 允许表单认证。针对/oauth/token端点。
-
添加开发配置tokenEndpointAuthenticationFilters
-
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);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
oauthServer
// 开启/oauth/token_key验证端口无权限访问
.tokenKeyAccess("permitAll()")
// 开启/oauth/check_token验证端口认证权限访问
.checkTokenAccess("isAuthenticated()");
}
3.1 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);
}
参考
Spring Security Oauth2配置类AuthorizationServerConfigurerAdapter