springsecurity oauth2.0

参考了Springboot2+SpringSecurity+Oauth2+Mysql数据库实现持久化客户端数据
1 基本环境搭建
1.1 数据库脚本
数据库脚本从官方spring-security-oauth中获取,根据你需要创建对应的表.我这里用到了下面几张表。
1
springsecurity oauth2.0_第1张图片
1.2 ouath2.0相关jar
引入对应的jar,与security和oauth2.0相关的


        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-securityartifactId>
            <version>2.3.12.RELEASEversion>
        dependency>

        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-oauth2artifactId>
            <version>2.2.5.RELEASEversion>
        dependency>

1.3 spring security相关配置

import com.dzmsoft.open.oauth.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserService userService;
    @Autowired
    private PasswordEncoder passwordEncoder;

    @Bean
    public PasswordEncoder myPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                // 匹配下面的路径放过
                .antMatchers("/test/01").permitAll()
                // 资源必须授权后访问
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginProcessingUrl("/login")
                .permitAll()//指定认证页面可以匿名访问
                //关闭跨站请求防护
                .and()
                .httpBasic()
                .and()
                .csrf().disable();
    }

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

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        //UserDetailsService类
        auth.userDetailsService(userService)
                //加密策略
                .passwordEncoder(passwordEncoder);
    }

}

1.4 授权服务器配置

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
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;
import org.springframework.security.oauth2.provider.approval.ApprovalStore;
import org.springframework.security.oauth2.provider.approval.JdbcApprovalStore;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;

import javax.sql.DataSource;

@Configuration
@EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private DataSource dataSource;
    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private CustomAuthenticationEntryPoint customAuthenticationEntryPoint;

    /**
     * 从数据库中查询出客户端信息
     * @return
     */
    @Bean
    public JdbcClientDetailsService clientDetailsService() {
        JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource);
        jdbcClientDetailsService.setPasswordEncoder(passwordEncoder);
        return jdbcClientDetailsService;
    }

    /**
     * token保存策略
     * @return
     */
    @Bean
    public TokenStore tokenStore() {
        return new JdbcTokenStore(dataSource);
    }

    /**
     * 授权信息保存策略
     * @return
     */
    @Bean
    public ApprovalStore approvalStore() {
        return new JdbcApprovalStore(dataSource);
    }

    /**
     * 授权码模式专用对象
     * @return
     */
    @Bean
    public AuthorizationCodeServices authorizationCodeServices() {
        return new JdbcAuthorizationCodeServices(dataSource);
    }

    @Bean
    public CustomWebResponseExceptionTranslator myWebResponseExceptionTranslator(){
        return new CustomWebResponseExceptionTranslator();
    }


    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(clientDetailsService());
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        //允许form表单客户端认证,允许客户端使用client_id和client_secret获取token
        security
//                .allowFormAuthenticationForClients()
                //通过验证返回token信息
//                .checkTokenAccess("isAuthenticated()")
                .checkTokenAccess("permitAll()")
                // 获取token请求不进行拦截
                .tokenKeyAccess("permitAll()")
                .passwordEncoder(passwordEncoder);
        CustomClientCredentialsTokenEndpointFilter endpointFilter = new CustomClientCredentialsTokenEndpointFilter(security);
        endpointFilter.afterPropertiesSet();
        endpointFilter.setAuthenticationEntryPoint(customAuthenticationEntryPoint);
        // 客户端认证之前的过滤器
        security.addTokenEndpointAuthenticationFilter(endpointFilter);


    }

    /**
     * OAuth2的主配置信息
     * @return
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.approvalStore(approvalStore())
                .authenticationManager(authenticationManager)
                .authorizationCodeServices(authorizationCodeServices())
                .tokenStore(tokenStore())
                .userDetailsService(userDetailsService)
                .exceptionTranslator(myWebResponseExceptionTranslator());
    }


}

1.5 entrypoint
SpringBoot的Endpoint主要是用来监控应用服务的运行状况,并集成在Mvc中提供查看接口
浅析@ResponseBodyAdvice的理解以及实际应用,ResponseBodyAdvice 接口是在 Controller 执行 return 之后,在 response 返回给客户端之前,执行的对 response 的一些处理,可以实现对 response 数据的一些统一封装或者加密等操作
下面可以看到对返回值的修正。

import com.dzmsoft.open.oauth.dto.CommonResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import java.util.LinkedHashMap;

@Slf4j
@ControllerAdvice
public class CustomResponseBodyAdvice implements ResponseBodyAdvice {
    @Override
    public boolean supports(MethodParameter methodParameter, Class aClass) {
        //此处返回true,表示对任何handler的responsebody都调用beforeBodyWrite方法,如果有特殊方法不使用可以考虑使用注解等方式过滤
        return true;
    }

    /**
     * 对Controller的所有返回结果进行处理
     * @param body 是controller方法中返回的值,对其进行修改后再return
     * @param methodParameter
     * @param mediaType
     * @param aClass
     * @param serverHttpRequest
     * @param serverHttpResponse
     * @return
     */
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        log.info("请求返回数据类型class="+ body.getClass().getName());
        if(body instanceof CommonResponse){
            return body;
        }
        CommonResponse<Object> response = new CommonResponse<>();
        if (body.toString().contains("error") && body.toString().contains("Unauthorized")){
            if(body instanceof LinkedHashMap){
                LinkedHashMap<String,String> map = (LinkedHashMap)body;
                map.remove("timestamp");
                map.remove("status");
                map.remove("message");
                response.fail(map);
            }else{
                response.fail(body);
            }
        }else{
            response.success(body);
        }

        if (log.isDebugEnabled()) {
            log.debug("请求返回数据body=     " + body.toString());
        }
        return response;
    }
}

异常返回

import com.alibaba.fastjson.JSON;
import com.dzmsoft.open.oauth.dto.CommonResponse;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;


@Component("customAuthenticationEntryPoint")
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
        response.setStatus(HttpStatus.OK.value());
        CommonResponse<Object> response1 = new CommonResponse<>();
        response1.fail( String.valueOf(HttpStatus.UNAUTHORIZED.value()),"client_id或client_secret错误",null);
        response.setHeader("Content-Type", "application/json;charset=utf-8");

        response.getWriter().print(JSON.toJSONString(response1));
        response.getWriter().flush();
    }

}

1.6 FrameworkEndpoint
oauth2.0等请求响应入口在spring-security-oauth2的jar中,@frameworkendpoint是@Controller的同义词
springsecurity oauth2.0_第2张图片
当然进去之前,先会进入ClientCredentialsTokenEndpointFilter
springsecurity oauth2.0_第3张图片
2 oauth2.0的四种模式
2.1 授权码模式

2.2 简化模式
2.3 密码模式

2.4 客户端模式
3 调整token的存储

你可能感兴趣的:(岁月会计云——通讯,岁月云——Web系统最佳实践,spring,boot,oauth2,spring,security)