OAuth2记录(密码模式)

解决问题:

  1. 自定义错误返回信息;(见第2节)
  2. 扩展token返回信息,利用AdditionalInformation增加附加信息;(见3.1)
  3. http basic验证方式;(见1.2及4.2)
  4. 当配置.permitAll()时,即使携带Token,也可以直接访问。(见第5节)

1.配置

1.1.pom依赖

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.16.RELEASE</version>
    <relativePath /> <!-- lookup parent from repository -->
</parent>

<dependencies>
    <!-- for Spring MVC -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- for Spring Security -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

    <!-- for OAuth 2.0 -->
    <dependency>
        <groupId>org.springframework.security.oauth</groupId>
        <artifactId>spring-security-oauth2</artifactId>
    </dependency>

</dependencies>

1.2.配置资源服务

    /**
     * 配置资源服务器
     */
    @Configuration
    @EnableResourceServer
    protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

        @Autowired
        private CustomAuthenticationEntryPoint customAuthenticationEntryPoint;
        @Autowired
        private CustomLogoutSuccessHandler customLogoutSuccessHandler;
        @Autowired
        private CustomAccessDeniedHandler customAccessDeniedHandler;
        @Override
        public void configure(HttpSecurity http) throws Exception {
            http
                    .logout()
                    .logoutUrl("/oauth/logout")
                    .logoutSuccessHandler(customLogoutSuccessHandler) //配置登出自定义接口
                    .and()
                    .authorizeRequests()
                    .antMatchers("/api/login").permitAll()
                    .antMatchers("/api/**").authenticated()
                    .antMatchers("/backstage/**").authenticated()
                    // .and().httpBasic() //添加该配置即可使用http basic验证方式(见4.2)
            ;
        }
        //配置自定义错误返回
        @Override
        public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
            resources.authenticationEntryPoint(customAuthenticationEntryPoint)
                    .accessDeniedHandler(customAccessDeniedHandler)
            ;
        }

    }

1.3.配置授权服务

    /**
     * 配置授权服务
     */
    @Configuration
    @EnableAuthorizationServer //实现EnvironmentAware接口可以重写其setEnvironment方法,用来读取配置文件的属性
    protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter implements EnvironmentAware {

         private static final String ENV_OAUTH = "authentication.oauth.";
         private static final String PROP_CLIENTID = "clientid";
        private static final String PROP_SECRET = "secret";
        private static final String PROP_TOKEN_VALIDITY_SECONDS = "tokenValidityInSeconds";

        private RelaxedPropertyResolver propertyResolver;//该类包含读取配置文件的方法

        @Autowired
        private DataSource dataSource;
        @Autowired
        private MyWebResponseExceptionTranslator myWebResponseExceptionTranslator;
        @Autowired
        private MyUserDetailsService myUserDetailsService;
        @Autowired
        @Qualifier("authenticationManagerBean")
        private AuthenticationManager authenticationManager;
        
        //使用 JdbcTokenStore(或使用自定义TokenStore)
        @Bean
        public TokenStore tokenStore() {
            return new JdbcTokenStore(dataSource);
        }
        //token扩展
        @Bean
        @ConditionalOnMissingBean(name = "tokenEnhancer")
        public TokenEnhancer tokenEnhancer() {
            return new MyTokenEnhancer();
        }      

        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints)
                throws Exception {
            endpoints
                    .tokenEnhancer(tokenEnhancer()) //token扩展
                    .tokenStore(tokenStore()) //tokenStore
                    .authenticationManager(authenticationManager) //开启密码授权模式
                    .userDetailsService(myUserDetailsService) //对用户详细信息的检查,比如查看是否存在
                    .exceptionTranslator(myWebResponseExceptionTranslator)
                    ;
        }

        // 可以用来定义一个基于内存的或者JDBC的客户端信息服务。
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            clients
                    .inMemory() //此处基于内存
                    .withClient(propertyResolver.getProperty(PROP_CLIENTID))
                    .scopes("read", "write")
                    .authorizedGrantTypes("password") //密码模式
                    .secret(propertyResolver.getProperty(PROP_SECRET))
                    .accessTokenValiditySeconds(propertyResolver.getProperty(PROP_TOKEN_VALIDITY_SECONDS, Integer.class, 1800)); //优先读配置文件,没有则用默认值
        }

        //实现EnvironmentAware接口可以重写其setEnvironment方法,用来读取配置文件的属性;
        @Override
        public void setEnvironment(Environment environment) {
            this.propertyResolver = new RelaxedPropertyResolver(environment, ENV_OAUTH);
        }

    }

 

  • 实现EnvironmentAware接口可以重写其setEnvironment方法,用来读取配置文件的属性;
  • @Configuration 注解,保证 OAuth2AuthorizationServer 能够被 SpringBoot 扫描到配置。

1.4.application.properties

############# oauth2 ######################
authentication.oauth.clientid=abc
authentication.oauth.secret=123456
authentication.oauth.tokenValidityInSeconds=18000
#将资源拦截的过滤器运行顺序放到第3个执行,也就是在oauth2的认证服务器后面执行
security.oauth2.resource.filter-order = 3

1.5.SpringSecurity相关配置

/**
 * Description:开启SpringSecurity配置
 * User: adg
 * Date: 2019/2/25
 */
@Configuration
@EnableWebSecurity
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
    //自定义UserDetailsService注入
    @Autowired
    private MyUserDetailsService userDetailsService;

    //配置匹配用户时密码规则
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    //配置全局设置
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        //设置UserDetailsService以及密码规则
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    //排除路径拦截
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/logout");
    }

    //跨域
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.requestMatchers().antMatchers(HttpMethod.OPTIONS, "/oauth/**","/logout")
                //支持跨域
                .and()
                .cors()
                .and()
                .csrf().disable();

    }

    //全局 CORS Filter
    @Bean
    public CorsFilter corsFilter() {
        final UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
        final CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowCredentials(true);
        corsConfiguration.addAllowedOrigin("*");
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");
        urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
        return new CorsFilter(urlBasedCorsConfigurationSource);
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

}

 

2.自定义错误返回

以下类都需在【1.2.配置资源服务】中配置

2.1.MyAuthenticationEntryPoint

//Description:自定义401错误码,请求未带token或token失效
@Component
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {……}

2.2.MyAccessDeniedHandler

//Description:自定义400禁止访问
@Slf4j
@Component("myAccessDeniedHandler")
public class MyAccessDeniedHandler implements AccessDeniedHandler {……}

以下类都需在【1.3.配置权限服务】中配置

2.3.MyWebResponseExceptionTranslator

//自定义异常解释器,可根据异常自定义返回信息,如账号密码错误
@Component
public class MyWebResponseExceptionTranslator implements WebResponseExceptionTranslator {
    @Override
    public ResponseEntity translate(Exception e) throws Exception {……}
}

2.4.MyUserDetailsService

//自定义UserDetailsService
@Component("myDetailsService")
public class MyUserDetailsService implements UserDetailsService {……}

 

3.自定义token返回

3.1.MyTokenEnhancer

public class MyTokenEnhancer implements TokenEnhancer {
    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken accessToken,
                                     OAuth2Authentication authentication) {
        if (accessToken instanceof DefaultOAuth2AccessToken) {
            DefaultOAuth2AccessToken token = ((DefaultOAuth2AccessToken) accessToken);
//            token.setValue(getNewToken());
//            OAuth2RefreshToken refreshToken = token.getRefreshToken();
//            if (refreshToken instanceof DefaultOAuth2RefreshToken) {
//                token.setRefreshToken(new DefaultOAuth2RefreshToken(getNewToken()));
//            }
            Map<String,String> mtoken = new HashMap<>();
            mtoken.put("token",accessToken.getValue());
            Map<String, Object> map = new HashMap<String, Object>();
            map.put("code", 0);
            map.put("resultMsg", "操作成功");
            map.put("data", mtoken);
            token.setAdditionalInformation(map);//附加信息
        }
        return accessToken;
    }
    private String getNewToken() {
        return "123" + UUID.randomUUID().toString().replace("-", "");
    }
}

 

4.前端获取token&请求接口

4.1.请求接口

获取token

  • /oauth/token

登出

  • 默认 /oauth/logout 自定义为 /logout

 

4.2.请求头

获取token时前端请求头加如下(标红为base64的 (格式—> clientId:password ,是clientId和其密码,不是用户名和密码)

Authorization: 'Basic 此处为base64的clientId:password'

访问接口时前端请求头加如下

Authorization: 'Bearer 5f476c06-bfee-4121-b8b4-47aa598bde5a'

Http基本认证【http baisc】

Authorization: 'Basic 此处为base64的用户名密码'

5.其他问题

5.1需求是当配置.permitAll()时,即使携带Token,也可以直接访问。

spring-security的认证为一系列过滤器链。我们只需定义一个比OAuth2AuthenticationProcessingFilter更早的过滤器拦截指定请求,去除header中的Authorization Bearer xxxx即可。

 

1、PermitAuthenticationFilter类

@Component("permitAuthenticationFilter")
@Slf4j
public class PermitAuthenticationFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

         log.info("当前访问的地址:{}", request.getRequestURI());
         String requestURI = request.getRequestURI();
        List<String> list = new ArrayList<>();
        list.add("/backstage/picture/search/list");
        list.add("/backstage/picture/searchBase64/list");
        list.add("/backstage/event/realTime/list");
        list.add("/api/event/realTime/list");

        if (list.contains(requestURI)) {

            request = new HttpServletRequestWrapper(request) {
                private Set<String> headerNameSet;

                @Override
                public Enumeration<String> getHeaderNames() {
                    if (headerNameSet == null) {
                        // first time this method is called, cache the wrapped request's header names:
                        headerNameSet = new HashSet<>();
                        Enumeration<String> wrappedHeaderNames = super.getHeaderNames();
                        while (wrappedHeaderNames.hasMoreElements()) {
                            String headerName = wrappedHeaderNames.nextElement();
                            if (!"Authorization".equalsIgnoreCase(headerName)) {
                                headerNameSet.add(headerName);
                            }
                        }
                    }
                    return Collections.enumeration(headerNameSet);
                }

                @Override
                public Enumeration<String> getHeaders(String name) {
                    if ("Authorization".equalsIgnoreCase(name)) {
                        return Collections.<String>emptyEnumeration();
                    }
                    return super.getHeaders(name);
                }

                @Override
                public String getHeader(String name) {
                    if ("Authorization".equalsIgnoreCase(name)) {
                        return null;
                    }
                    return super.getHeader(name);
                }
            };

        }
        filterChain.doFilter(request, response);

    }
}

2、配置PermitAllSecurityConfig

/**
 * 配置过滤器
 */
@Component("permitAllSecurityConfig")
public class PermitAllSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain,HttpSecurity> {

    @Autowired
    private Filter permitAuthenticationFilter;

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.addFilterBefore(permitAuthenticationFilter, OAuth2AuthenticationProcessingFilter.class);
    }
}

3、在资源服务器配置中 对 PermitAllSecurityConfig 授权

@Override
public void configure(HttpSecurity http) throws Exception {
    http
        ……
        .and()
        .apply(permitAllSecurityConfig)
        ……
        .authorizeRequests()
        .antMatchers("permit/login").permitAll()
        ……

 

oauth的设计是用户不向第三方暴露自己的登陆信息的情况下,授权第三方以自己的身份访问一些资源。

oauth2的设计初衷是源于oauth1计算签名的复杂度,希望进一步降低第三方开发者的开发门槛。

 

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值