常规权限校验
- 1.保存用户信息到内存,并保持会话(有状态的会话保持)
- 2.jsessionId存储浏览器cookie,可能会发生csrf攻击
- 3.如果用户量大,会话会把内存占满
- 如果是分布式服务,第一次登陆请求保存在机器1,第二次去查询时访问的是机器2,此时怎么解决?
- 分布式session
- 如果是分布式服务,第一次登陆请求保存在机器1,第二次去查询时访问的是机器2,此时怎么解决?
- 4.在单一架构中,拿到sessionId,就能拿到用户信息(和sessionId绑定)
- 但是要尽量避免这种有状态的会话,容易发生csrf的跨域攻击
- 要做到请求跟用户信息无关,oauth2.0标准就出现了
- 但是要尽量避免这种有状态的会话,容易发生csrf的跨域攻击
oauth2
四种授权方式
授权码模式
简化模式,基本不用
密码模式
客户端模式
- 下游系统去认证服务器获取token令牌,token令牌是跟具体客户端相关的,跟用户无关,比如pc、安卓、ios都是不同的
- 多了一个认证服务器
- 用户发送的请求,会去认证服务器拿一个token,然后经过zuul网关路由到下游系统,下游系统会去认证服务器校验这个token,验证token是否有效,并且是在有效期内
- token只是一个用户访问权限的一个字符串,是跟用户信息无关的
微服务权限校验
- 在zuul是否应该做权限校验?
- 一般情况不做,因为是没有资源信息的,只是单纯的路由,但是下游系统必须做权限校验
- 也有只在zuul做权限校验,下游系统不做,但是做了防火墙的白名单,只允许zuul访问,同时也是内网(网络隔离)
- 如果极端情况,内网被攻破,那么整个系统都是危险的,因为下游系统没有做权限校验
- 所以zuul什么都不做,下游系统既做token的权限校验,又做防火墙的白名单,这样才是比较安全的权限校验
获取token的方式
客户端模式
- 以微信小程序为例
- 1.申请平台认证,AppId,AppSecurityId
- 2.id passward grant_type,账号、密码、授权方式
密码模式
授权码模式
认证服务器搭建—客户端模式
jar包
-
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-data</artifactId> </dependency>
启动类
-
package com.xiangxue.jack; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; @SpringBootApplication @EnableEurekaClient /* * EnableResourceServer注解开启资源服务,因为程序需要对外暴露获取token的API和验证token的API所以该程序也是一个资源服务器 * */ @EnableResourceServer public class MicroSecurityApplication { public static void main(String[] args) { SpringApplication.run(MicroSecurityApplication.class,args); } }
- @EnableResourceServer
配置类
-
package com.xiangxue.jack.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 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.token.TokenStore; @Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired private AuthenticationManager authenticationManager; @Autowired private RedisConnectionFactory connectionFactory; @Autowired private UserDetailsService userDetailsService; // 使用redis保存token @Bean public TokenStore tokenStore() { return new MyRedisTokenStore(connectionFactory); } /* * AuthorizationServerEndpointsConfigurer:用来配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)。 * */ @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { // authenticationManager是鉴权管理类 endpoints .authenticationManager(authenticationManager) .userDetailsService(userDetailsService)//若无,refresh_token会有UserDetailsService is required错误 .tokenStore(tokenStore()). allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST); } /* * AuthorizationServerSecurityConfigurer 用来配置令牌端点(Token Endpoint)的安全约束 * */ @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { // 允许表单认证 security.allowFormAuthenticationForClients().tokenKeyAccess("permitAll()") .checkTokenAccess("isAuthenticated()"); } /* ClientDetailsServiceConfigurer:用来配置客户端详情服务(ClientDetailsService),客户端详情信息在这里进行初始化,你能够把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息 * 1.授权码模式(authorization code) 2.简化模式(implicit) 3.密码模式(resource owner password credentials) 4.客户端模式(client credentials) * */ @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { String finalSecret = "{bcrypt}" + new BCryptPasswordEncoder().encode("123456"); // 代码写死了 // micro-web和micro-zuul clients. // jdbc(dataSource). inMemory(). withClient("micro-web") .resourceIds("micro-web") .authorizedGrantTypes("client_credentials", "refresh_token") .scopes("all","read", "write","aa") .authorities("client_credentials") .secret(finalSecret) .accessTokenValiditySeconds(1200) .refreshTokenValiditySeconds(50000) .and() .withClient("micro-zuul") .resourceIds("micro-zuul") .authorizedGrantTypes("password", "refresh_token") .scopes("server") .authorities("password") .secret(finalSecret) .accessTokenValiditySeconds(1200) .refreshTokenValiditySeconds(50000); } }
- @EnableAuthorizationServer
- 继承AuthorizationServerConfigurerAdapter
另一个配置类
-
package com.xiangxue.jack.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; 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.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.provisioning.InMemoryUserDetailsManager; @Configuration @EnableWebSecurity public class SecurityConfiguration extends WebSecurityConfigurerAdapter { // UserDetailsService用来管理用户信息 @Bean @Override protected UserDetailsService userDetailsService() { BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); String finalPassword = "{bcrypt}" + bCryptPasswordEncoder.encode("123456"); InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); manager.createUser(User.withUsername("jack").password(finalPassword).authorities("USER").build()); manager.createUser(User.withUsername("admin").password(finalPassword).authorities("USER").build()); return manager; } /*@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService()).passwordEncoder(new BCryptPasswordEncoder());; }*/ @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { AuthenticationManager manager = super.authenticationManagerBean(); return manager; } // 使用http鉴权方式 // "/oauth/**","/actuator/**"这个两个接口时允许访问的 @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable(); // http.requestMatchers().anyRequest() // .and() // .authorizeRequests() // .antMatchers("/oauth/**").permitAll(); http.authorizeRequests() .antMatchers("/oauth/**","/actuator/**").permitAll() .and() .httpBasic().disable(); } }
- @EnableWebSecurity
启动测试
- 按顺序启动Eureka、MicroConfigServer、MicroSecurity
- 测试访问:localhost:3030/oauth/token,post请求
- postman_body_form-data
- grant_type = client_credentials
- client_id = micro-web
- client_secret = 123456
- scope = all (匹配的字符串,也是在代码中配置的)
- postman_body_form-data
总结
- 客户端模式不常用,控制粒度比较小,只要拿到token就能访问
根据token访问
- 保留上面的启动,继续启动MicroWebSecurity、MicroZuulSecurity
- 再次获取token,请求:localhost:7070/web/user/queryUser,get请求
- Headers中,key=Authorization,value=bearer(获取token中的token类型)+" " + 获取的token
- 调用失败,去掉链路追踪的properties配置和jar包
- 调用失败,修改github配置,路由配置错误了
- Headers中,key=Authorization,value=bearer(获取token中的token类型)+" " + 获取的token