微服务集成Spring Security + Oauth2 + JWT+Swagger2 + Druid

参考《深入理解Spring Cloud与微服务构建》 感谢作者 方志朋
参考 https://blog.csdn.net/yuanlaijike/category_9283872.html
参考 https://www.jianshu.com/p/19059060036b

背景介绍

本文是下文的进阶篇,进一步以微服务为基础进行集成Spring Security + Oauth2 + JWT+Swagger2 + Druid
微服务自动化部署SpringCloud+Dockerfile+docker-compose+git+Maven

实现目标

把网关服务和验证服务集成到一个微服务中,验证服务以oauth2+jwt进行实现,同时使用swagger2简单明了的展示验证登入的相关接口,druid方便管理数据库连接池以及性能排查

  • 模块authservice
    • 集成网关zuul 取消原ui模块网关
    • 集成Spring Security Oauth2验证+鉴权
      • Oauth server 获取令牌token
      • Oauth client 用户注册API
  • GibHub项目代码 分支master

实现过程

集成网关zuul
//注意 Spring Boot 1.2x的zuul与oauth2集成会报spring注入错误和unable to start embedded tomcat错误
//Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: private org.springframework.cloud.security.oauth2.resource.ResourceServerProperties org.springframework.cloud.security.oauth2.proxy.OAuth2ProxyAutoConfiguration.resourceServerProperties; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.springframework.cloud.security.oauth2.resource.ResourceServerProperties] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
//参考:https://github.com/spring-cloud/spring-cloud-security/issues/73
//解决办法是升级 Spring Cloud 版本为Brixton.SR5,Spring Boot版本为1.3.5
//新增模块 authservice,网关zuul可参考ui模块配置
//包含 config、eureka、feign、ribbon、hystrix、zuul

//config模块添加文件 authservice.yml
ribbon:
  ReadTimeout: 60000
  ConnectTimeout: 20000

zuul:
  host:
    connect-timeout-millis: 20000
    socket-timeout-millis: 60000

  routes:
    authservice:
      path: /uiservice/**
      # 相当于把http://localhost/uiservice/xxx中的/xxx段映射到原来的http://localhost/ui/xxx 
      serviceId: ui
      sensitiveHeaders:
集成oauth2
//添加依赖oauth2,默认包含security、oauth2、jwt、lombok包
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>

//oauth2是实现Spring Security验证模块,所以先直接进行Spring Security的配置
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class GlobalMethodSecurityConfiguration {

}

@Configuration
class WebSecurityConfig extends WebSecurityConfigurerAdapter {
   
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //CSRF:因为不再依赖于Cookie,所以你就不需要考虑对CSRF(跨站请求伪造)的防范。
        http
            .csrf().disable()
                .exceptionHandling()
                .authenticationEntryPoint(new AuthenticationEntryPoint() {
                    @Override
                    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
                        httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
                    }
                })
                .and()
                .authorizeRequests()
                .antMatchers("/**").authenticated()
            .and()
                .httpBasic();
    }

    @Autowired
    UserServiceDetail userServiceDetail;
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userServiceDetail)
                .passwordEncoder(new BCryptPasswordEncoder());
    }
}

//注意上面注入的userServiceDetail需要自己实现接口UserDetailsService,security jpa实体表可以自动创建,但是oauth的表需要自行创建,语句见源码的sql.sql
@Service
public class UserServiceDetail implements UserDetailsService {
   
    @Autowired
    private UserDao userRepository;
    
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return userRepository.findByUsername(username);
    }
}

//同时也是资源服务器,配置如下
@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter{
    Logger log = LoggerFactory.getLogger(ResourceServerConfiguration.class);

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .authorizeRequests()
                .regexMatchers(".*swagger.*",".*v2.*",".*webjars.*","/user/login.*","/user/registry.*","/user/test.*",".*druid.*").permitAll()
                .antMatchers("/**").authenticated();
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        log.info("Configuring ResourceServerSecurityConfigurer ");
        resources.resourceId("authservice").tokenStore(tokenStore);
    }

    @Autowired
    TokenStore tokenStore;
}

//oauth2配置,定义内部oauth server的basic验证用户名密码,并加入jwt私钥解密方式
@Configuration
@EnableAuthorizationServer
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("authservice")
                .secret("123456")
                .scopes("service")
                .autoApprove(true)
                .authorizedGrantTypes("implicit","refresh_token", "password", "authorization_code")
                .accessTokenValiditySeconds(24*3600);//24小时过期
    }

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

    @Autowired
    @Qualifier("authenticationManagerBean")
    private AuthenticationManager authenticationManager;

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtTokenEnhancer());
    }

    @Bean
    protected JwtAccessTokenConverter jwtTokenEnhancer() {
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("fzp-jwt.jks"), "fzp123".toCharArray());
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setKeyPair(keyStoreKeyFactory.getKeyPair("fzp-jwt"));
        return converter;
    }
}

//jwt配置,公钥加密方式
@Configuration
public class JwtConfiguration {
    @Autowired
    JwtAccessTokenConverter jwtAccessTokenConverter;

    @Bean
    @Qualifier("tokenStore")
    public TokenStore tokenStore() {

        System.out.println("Created JwtTokenStore");
        return new JwtTokenStore(jwtAccessTokenConverter);
    }

    @Bean
    protected JwtAccessTokenConverter jwtTokenEnhancer() {
        JwtAccessTokenConverter converter =  new JwtAccessTokenConverter();
        Resource resource = new ClassPathResource("public.cert");
        String publicKey ;
        try {
            publicKey = new String(FileCopyUtils.copyToByteArray(resource.getInputStream()));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        converter.setVerifierKey(publicKey);
        return converter;
    }
}


集成swagger2
//加入依赖
 <dependency>
     <groupId>io.springfox</groupId>
     <artifactId>springfox-swagger2</artifactId>
     <version>2.7.0</version>
 </dependency>
 <dependency>
     <groupId>io.springfox</groupId>
     <artifactId>springfox-swagger-ui</artifactId>
     <version>2.7.0</version>
 </dependency>

//配置swagger2
@Configuration
@EnableSwagger2
public class SwaggerConfig {
    /**
     * 全局参数
     *
     * @return
     */
    private List<Parameter> parameter() {
        List<Parameter> params = new ArrayList<>();
        params.add(new ParameterBuilder().name("Authorization")
                .description("Authorization Bearer token")
                .modelRef(new ModelRef("string"))
                .parameterType("header")
                .required(false).build());
        return params;
    }

    @Bean
    public Docket sysApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.xiaofeng.authservice.controller"))
                .paths(PathSelectors.any())
                .build().globalOperationParameters(parameter());
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title(" authservice api ")
                .description("authservice 微服务")
                .termsOfServiceUrl("")
                .contact("xiaofeng")
                .version("1.0")
                .build();
    }
}

//添加controller
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    UserService userService;

    @ApiOperation(value = "注册", notes = "username和password为必选项")
    @RequestMapping(value = "/registry",method = RequestMethod.POST)
    public User createUser(@RequestBody User user){
        //参数判读省略,判读该用户在数据库是否已经存在省略
        String entryPassword= BPwdEncoderUtils.BCryptPassword(user.getPassword());
        user.setPassword(entryPassword);
        return userService.createUser(user);
    }

    @ApiOperation(value = "登录", notes = "username和password为必选项")
    @RequestMapping(value = "/login",method = RequestMethod.POST)
    public RespDTO login(@RequestParam String username , @RequestParam String password){
        //参数判读省略
      return   userService.login(username,password);
    }

    @ApiOperation(value = "根据用户名获取用户", notes = "根据用户名获取用户")
    @RequestMapping(value = "/{username}",method = RequestMethod.POST)
    @PreAuthorize("hasRole('USER')")
   // @PreAuthorize("hasAnyAuthority('ROLE_USER')")
    public RespDTO getUserInfo(@PathVariable("username") String username){
        //参数判读省略
        User user=  userService.getUserInfo(username);
        return RespDTO.onSuc(user);
    }
}

集成Druid
//数据库连接池druid
//config模块的文件 authservice.yml添加
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/springcloud?useUnicode=true&characterEncoding=utf-8
    username: root
    password: root
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true
    maxPoolPreparedStatementPerConnectionSize: 20
    filters: stat,wall,log4j
    connectionProperties:
      druid.stat.mergeSql: true
      druid.stat.slowSqlMillis: 5000
    useGlobalDataSourceStat: true
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true

//Druid配置
@Configuration
public class DruidConfiguration {

    @Bean
    public ServletRegistrationBean DruidStatViewServle2() {
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
        //白名单:
        servletRegistrationBean.addInitParameter("allow", "127.0.0.1");
        //IP黑名单 (存在共同时,deny优先于allow) : 如果满足deny的话提示:Sorry, you are not permitted to view this page.
        //servletRegistrationBean.addInitParameter("deny", "192.168.1.73");
        //登录查看信息的账号密码.
        servletRegistrationBean.addInitParameter("loginUsername", "admin2");
        servletRegistrationBean.addInitParameter("loginPassword", "123456");
        //是否能够重置数据.
        servletRegistrationBean.addInitParameter("resetEnable", "false");
        return servletRegistrationBean;
    }

    /**
     * 注册一个:filterRegistrationBean
     *
     * @return
     */
    @Bean
    public FilterRegistrationBean druidStatFilter2() {

        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter());

        //添加过滤规则.
        filterRegistrationBean.addUrlPatterns("/*");

        //添加不需要忽略的格式信息.
        filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        return filterRegistrationBean;
    }

}

验证功能

//模拟登入
curl -X POST -d username=admin -d password=admin http://localhost:9999/user/login
//模拟从oauth server 获取token
curl -X POST -u authservice:123456 http://localhost:9999/oauth/token -d grant_type=password -d username=admin -d password=admin
//模拟从oauth server 获取token 另一种写法
curl -X POST -H "Authorization: Basic YXV0aHNlcnZpY2U6MTIzNDU2" -d grant_type=password -d username=admin -d password=admin http://localhost:9999/oauth/token
//模拟用登入得到token进行访问有权限的controll接口
curl -X POST -H "Authorization:BearereyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE1Nzg1Mzc4NDIsInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiVVNFUiJdLCJqdGkiOiI4Y2JkYjRjMy0wOTMxLTQyMWItYWYwOS1lMDViYzYyODFhY2YiLCJjbGllbnRfaWQiOiJhdXRoc2VydmljZSIsInNjb3BlIjpbInNlcnZpY2UiXX0.JNdRVRcOau0uTmUdSVdImeQPcIf6PcBzIL3bdfm0856ou9EjEiGDbqike1nw2DueR3Kq5AnbtsYuiPA_sEuwamFLy3H2Eezo7y-5DX26fId7PufHWn1aSshsW5zQNGORr47xZ8_oXq2J5yfwzCrDNDzqbgkcOAB7jWTD9DcOPUig2FCvA0AglZxt442W34N_Sds6l8C6Hy9Dl2hlzAoe0VCy_yCv2APnwNhX4KWnFJTZsEK9LeYgwvlM0nPz6JOYwOlLSk4P8geC0zuspoJ0Ve9mXU4qHzX040amrSjnJooLL1jmsxDVffop6rprkQmuKSkEDfipVfRLx5TUB9xv4g","token_type":"bearer","refresh_token":"eyJhbGciOiJSUzI1NiJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbInNlcnZpY2UiXSwiYXRpIjoiOGNiZGI0YzMtMDkzMS00MjFiLWFmMDktZTA1YmM2MjgxYWNmIiwiZXhwIjoxNTgxMDQzNDQyLCJhdXRob3JpdGllcyI6WyJVU0VSIl0sImp0aSI6IjFjNzc3ZjE4LWU0YjEtNGYzNi05ZTE5LTIwYzZiNDRmZGMyOCIsImNsaWVudF9pZCI6ImF1dGhzZXJ2aWNlIn0.YmcMlCzEmLtN_W6XrlP3O5EkH_jbH6Gpa8rxiuVRpW6k9R3k77ZZauE07f1v_dyUL3DGRzuMMGDGfKHOUapJ9gus2UX9-QDe9x9V46hEVkfcHplYwwdC43o8Z6URM4rlA5vJkKbQa6EI1KJVZfLNkfTSXjE2TD3M2MuwJu4xgkNg6Eg25vDxyiFPIsyOBIl66ROJJogS90M7tMrOiCTK40jWTPwrDfOdy7EzJvi0mlCwZmGK9qP2pwB8yi5zcgHT2P0XrPFT_VPDQJS5X7DLU_k-k_mfoHyobtVHoDF7VkUdLngtoqy_ynF-hcJqvHH8PvnwjaVzi448dGpXXBSaxA" http://localhost:9999/user/admin
  • Druid监控
    打开druid监控网址:http://localhost/druid/login.html
    用户名密码为配置类中设置的admin2/123456
    登入后可以查看数据库各种相关信息
    在这里插入图片描述
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值