OAuth2 授权认证服务器

281 阅读2分钟

认证服务器

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;

/**
 * 授权服务器配置 添加 BasicAuthenticationFilter ,主要针对 Basic XXX 请求头拦截 暴露oauth2端点暴露
 *
 * @description EnableAuthorizationServer 启用授权服务
 * @description extends AuthorizationServerConfigurerAdapter
 */
@Configuration
@EnableAuthorizationServer
public class OAuth2ServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private AuthenticationManager authenticationManager;


    /**
     * authorization_code:
     * 请求示例:http://localhost:17000/oauth/authorize?client_id=admin&redirect_uri=http://localhost:8080/test&response_type=code
     * redirect_uri,必须在redirectUris白名单集合中
     * response_type = code
     * autoApprove,false情况下,无法转发到 “/oauth/confirm_access” 端点
     * {@link org.springframework.security.oauth2.provider.endpoint.WhitelabelApprovalEndpoint}
     * code会通过redirect_uri 回传
     * <p>
     * password:
     * 请求示例:[post]http://localhost:17000/oauth/token?grant_type=password&username=test&password=test1
     * header需要包含 Authorization:Basic YWRtaW46YWRtaW4x(clientId:clientSecret拼接后base64)
     * 需要认证管理器 {@link com.xrj.cloud.authorize.security.WebSecurityConfig} authenticationManagerBean()和daoAuthenticationProvider()
     * <p>
     * client_credentials:
     * 请求示例:[post]http://localhost:17000/oauth/token?grant_type=client_credentials
     * header需要包含 Authorization:Basic YWRtaW46YWRtaW4x(clientId:clientSecret拼接后base64)
     * <p>
     * implicit:
     * 请求示例:http://localhost:17000/oauth/authorize?client_id=admin&redirect_uri=http://localhost:8080/test&response_type=token&state=121
     * response_type = token
     * state = 1432143
     * 和授权码模式相似,response_type有变动,改为直接获取token,scope权限,state用于认证标记,传过去什么回调时传回来什么
     *
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("admin")
                .secret(passwordEncoder.encode("admin1"))
                .accessTokenValiditySeconds(3600)
                .refreshTokenValiditySeconds(864000)
                .redirectUris("http://localhost:8080/test") //转发回调白名单
                .scopes("read", "user")
                .autoApprove(false)
                .autoApprove("user")
                .authorizedGrantTypes("authorization_code", "implicit", "password", "refresh_token", "client_credentials");
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints)
            throws Exception {
        endpoints.authenticationManager(authenticationManager);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) {
        security.tokenKeyAccess("isAuthenticated()"); // 获取密钥需要身份认证,使用单点登录时必须配置
    }
}

import com.xrj.cloud.authorize.service.UsernameUserDetailsService;
import com.xrj.cloud.common.config.security.provider.SimpleAuthenticationProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * 安全配置
 *
 * @ EnableWebSecurity 启用web安全配置
 * @ EnableGlobalMethodSecurity 启用全局方法安全注解,就可以在方法上使用注解来对请求进行过滤
 * @ EnableResourceService 开启 OAuth2AuthenticationProcessingFilter 过滤器
 */
@Configuration
@Order(10)
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UsernameUserDetailsService userDetailsService;
    @Autowired
    private PasswordEncoder passwordEncoder;

    /**
     * 需要配置这个支持password模式
     * password grant type support.
     */
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }


    @Bean
    public SimpleAuthenticationProvider daoAuthenticationProvider() {
        SimpleAuthenticationProvider provider = new SimpleAuthenticationProvider();
        // 设置userDetailsService
        provider.setUserDetailsService(userDetailsService);
        // 使用BCrypt进行密码的hash
        provider.setPasswordEncoder(passwordEncoder);
        return provider;
    }

}

常见问题

  • 授权码模式下,无法转发授权页 ,应该是重写引起的。
//重写后,针对WebMvcConfigurationSupport类的自动装配失效
public class MvcConfig extends WebMvcConfigurationSupport {


    //重写,增加解析器
    @Override
    protected void configureViewResolvers(ViewResolverRegistry registry) {
        registry.viewResolver(new InternalResourceViewResolver());
    }

}
  • Fastjson和Jackson不一致问题。在单点登录时,如果修改了默认转换器,应该会碰到的问题。

import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;

import java.io.IOException;

import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8;

public class OAuth2HttpMessageConverter extends AbstractHttpMessageConverter<DefaultOAuth2AccessToken> {
 
    private final MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
 
    public OAuth2HttpMessageConverter() {
        super(APPLICATION_JSON_UTF8);
    }
 
    @Override
    protected boolean supports(Class<?> clazz) {
        return clazz.equals(DefaultOAuth2AccessToken.class);
    }
 
    @Override
    protected DefaultOAuth2AccessToken readInternal(Class<? extends DefaultOAuth2AccessToken> clazz, HttpInputMessage inputMessage)
            throws IOException, HttpMessageNotReadableException {
        throw new UnsupportedOperationException(
                "This converter is only used for converting DefaultOAuth2AccessToken to json.");
    }
 
    @Override
    protected void writeInternal(DefaultOAuth2AccessToken accessToken, HttpOutputMessage outputMessage) throws IOException,
            HttpMessageNotWritableException {
        mappingJackson2HttpMessageConverter.write(accessToken, APPLICATION_JSON_UTF8, outputMessage);
    }
}