文章目录
1. Spring Security 与 OAuth2
Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架,主要内容包括 认证(Authentication) 和 授权(Authorization)!
- 认证(Authentication)
- 用户认证就是判断一个用户的身份是否合法的过程,用户去访问系统资源时系统要求验证用户的身份信息,身份合法方可继续访问,不合法则拒绝访问。
- 常见的用户身份认证方式有:用户名密码登录,二维码登录,手机短信登录,指纹认证等方式。
- 授权(Authorization)
- 授权是用户认证通过根据用户的权限来控制用户访问资源的过程,拥有资源的访问权限则正常访问,没有权限则拒绝访问。
将OAuth2和Spring Security集成,就可以得到一套完整的安全解决方案Spring Security OAuth2。本章我们将使用Spring Security OAuth2构建一个授权服务器来验证用户身份以提供access_token,并使用这个access_token来从资源服务器请求数据。
2. Spring Security OAuth2的配置和使用
①:引入依赖
如果是spring boot项目,需要引入
//spring整合security <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> //security整合outh2 <dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth2</artifactId> <version>2.3.4.RELEASE</version> </dependency>
如果是spring cloud项目,需要引入
//只需引入这一个即可:此依赖整合了security、oauth2 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> </dependency> // spring cloud <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Hoxton.SR8</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
②:配置 spring security
@Configuration @EnableWebSecurity //这个可以不用写,磁层默认支持了 public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } //自己的类实现了UserDetailsService,注入即可 @Autowired private UserService userService; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { // 获取用户信息 auth.userDetailsService(userService); } @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() //登录请求直接通过 .permitAll() //所有请求过来 .and().authorizeRequests() // oauth/** 开头的请求直接通过,因为要访问outh2授权服务器 .antMatchers("/oauth/**").permitAll() //其他所有请求都要进行认证 .anyRequest().authenticated() //登出请求直接通过 .and().logout().permitAll() //解决跨域 .and().csrf().disable(); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { // oauth2 密码模式需要拿到这个bean return super.authenticationManagerBean(); } }
用户信息类UserService
//用户信息可以使用使用一个类 实现UserDetailsService @Service public class UserService implements UserDetailsService { @Autowired @Lazy private PasswordEncoder passwordEncoder; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { String password = passwordEncoder.encode("123456"); //可以去查数据库,这里直接new return new User("fox",password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin")); } }
③:配置授权服务器
@Configuration @EnableAuthorizationServer public class AuthorizationServerConfig2 extends AuthorizationServerConfigurerAdapter { @Autowired private PasswordEncoder passwordEncoder; @Autowired private AuthenticationManager authenticationManagerBean; @Autowired private UserService userService; @Autowired private TokenStore tokenStore; /** * 密码模式需要outh2授权服务器去校验账号、密码 * 账号、密码在整合security时,放在security中了 * 所以需要整合security的授权管理器authenticationManagerBean */ @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.authenticationManager(authenticationManagerBean) //使用密码模式需要配置 .tokenStore(tokenStore) //指定token存储到redis,还有数据库、内存、jwt等存储方式! .reuseRefreshTokens(false) //refresh_token是否重复使用 .userDetailsService(userService) //这一步包含账号、密码的检查! .allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST); //支持GET,POST请求 } @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { //允许表单认证 security.allowFormAuthenticationForClients() // 配置校验token需要带入clientId 和clientSeret配置 .checkTokenAccess("isAuthenticated()"); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { /** *授权码模式 *http://localhost:8080/oauth/authorize?response_type=code&client_id=client&redirect_uri=http://www.baidu.com&scope=all *http://localhost:8080/oauth/authorize?response_type=code&client_id=client * * implicit: 简化模式 *http://localhost:8080/oauth/authorize?client_id=client&response_type=token&scope=all&redirect_uri=http://www.baidu.com * * password模式 * http://localhost:8080/oauth/token?username=fox&password=123456&grant_type=password&client_id=client&client_secret=123123&scope=all * * 客户端模式 * http://localhost:8080/oauth/token?grant_type=client_credentials&scope=all&client_id=client&client_secret=123123 * * 刷新令牌 * http://localhost:8080/oauth/token?grant_type=refresh_token&client_id=client&client_secret=123123&refresh_token=[refresh_token值] */ clients.inMemory() //配置client_id .withClient("client") //配置client-secret .secret(passwordEncoder.encode("123123")) //配置访问token的有效期 .accessTokenValiditySeconds(3600) //配置刷新token的有效期 .refreshTokenValiditySeconds(864000) //配置redirect_uri,用于授权成功后跳转 .redirectUris("http://www.baidu.com") //配置申请的权限范围 .scopes("all") /** * 配置grant_type,表示授权类型 * authorization_code: 授权码模式 * implicit: 简化模式 * password: 密码模式 * client_credentials: 客户端模式 * refresh_token: 更新令牌 */ .authorizedGrantTypes("authorization_code","implicit","password","client_credentials","refresh_token"); } }
④:配置redis、并生成 RedisTokenStore 的token存储方式
spring: redis: host: 127.0.0.1 database: 0
@Configuration public class RedisConfig { @Autowired private RedisConnectionFactory redisConnectionFactory; @Bean public TokenStore tokenStore(){ return new RedisTokenStore(redisConnectionFactory); } }
2. Spring Security OAuth2 整合 JWT
不懂JWT的可以点击查看此文章! Spring Security OAuth2 整合 JWT过程与第1节的步骤类似,都有如下几步
- ①:引入依赖,与第1节步骤不同,此处需要引入jwt依赖
- ②:配置 spring security
- ③:配置授权服务器
- ④:配置 token 存储方式,与第1节步骤不同,此处需要引入JwtTokenStore 的token存储方式!
只不过第①步和第④步稍有不同罢了,此处只将不同点列出!
①:引入依赖
以spring cloud项目为例,需要引入
// <!--JWT依赖--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> // <!--security 整合 jwt--> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-jwt</artifactId> <version>1.0.9.RELEASE</version> </dependency> //只需引入这一个即可:此依赖整合了security、oauth2 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> </dependency> // spring cloud <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Hoxton.SR8</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
②:生成 JwtTokenStore 的token存储方式
/** * 使用Jwt存储token的配置 */ @Configuration public class JwtTokenStoreConfig { @Bean public TokenStore jwtTokenStore(){ //传入一个 token转换器 return new JwtTokenStore(jwtAccessTokenConverter()); } //生成一个 token转换器 @Bean public JwtAccessTokenConverter jwtAccessTokenConverter(){ JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter(); //配置JWT使用的秘钥、盐值123123 accessTokenConverter.setSigningKey("123123"); return accessTokenConverter; } // JwtTokenEnhancer :自定义增强器 //把 token增强器 JwtTokenEnhancer 放入容器 @Bean public JwtTokenEnhancer jwtTokenEnhancer() { return new JwtTokenEnhancer(); } }
这样就Spring Security OAuth2 就与 JWT 整合成功了,解密时就必须拿上文配置的盐值123123来进行解密,否则解密失败!
//解密代码! Jwts.parser() .setSigningKey("123123".getBytes(StandardCharsets.UTF_8)) .parseClaimsJws(token) .getBody();
如何对 token 进行增强?
Outh2提供了一个接口TokenEnhancer,可以对token进行增强,只需实现该接口即可
//token内容增强器 //注意需要注入容器 @Component public class JwtTokenEnhancer implements TokenEnhancer { @Override public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { Map<String, Object> info = new HashMap<>(); //为原有的token的载荷增加一些内容 //在对token进行解密时,就可以拿到这里添加的信息 info.put("enhance", "enhance info"); ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info); return accessToken; } }
然后把该增强器配置进outh2的授权服务器即可!这样在解析token时就可以解析到增强的内容!
@Configuration @EnableAuthorizationServer public class AuthorizationServerConfig3 extends AuthorizationServerConfigurerAdapter { @Autowired private PasswordEncoder passwordEncoder; @Autowired private AuthenticationManager authenticationManagerBean; @Autowired private UserService userService; @Autowired @Qualifier("jwtTokenStore") private TokenStore tokenStore; @Autowired private JwtAccessTokenConverter jwtAccessTokenConverter; @Autowired private JwtTokenEnhancer jwtTokenEnhancer; @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { //配置JWT的内容增强器,TokenEnhancer可以对token进行增强! TokenEnhancerChain enhancerChain = new TokenEnhancerChain(); List<TokenEnhancer> delegates = new ArrayList<>(); //添加token增强器 delegates.add(jwtTokenEnhancer); //添加转换器 delegates.add(jwtAccessTokenConverter); //把增强内容放入 增强链中 enhancerChain.setTokenEnhancers(delegates); endpoints.authenticationManager(authenticationManagerBean) //使用密码模式需要配置 .tokenStore(tokenStore) //配置存储令牌策略 .accessTokenConverter(jwtAccessTokenConverter) .tokenEnhancer(enhancerChain) //配置token增强链 .reuseRefreshTokens(false) //refresh_token是否重复使用 .userDetailsService(userService) //刷新令牌授权包含对用户信息的检查 .allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST); //支持GET,POST请求 } @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { //允许表单认证 security.allowFormAuthenticationForClients() // 获取密钥需要身份认证,使用单点登录时必须配置 .tokenKeyAccess("isAuthenticated()"); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { /** *授权码模式 *http://localhost:8080/oauth/authorize?response_type=code&client_id=client&redirect_uri=http://localhost:8081/login&scope=all *http://localhost:8080/oauth/authorize?response_type=code&client_id=client * * password模式 * http://localhost:8080/oauth/token?username=fox&password=123456&grant_type=password&client_id=client&client_secret=123123&scope=all * * 客户端模式 * http://localhost:8080/oauth/token?grant_type=client_credentials&scope=all&client_id=client&client_secret=123123 * * 刷新令牌 * http://localhost:8080/oauth/token?grant_type=refresh_token&client_id=client&client_secret=123123&refresh_token=[refresh_token值] */ clients.inMemory() //配置client_id .withClient("client") //配置client-secret .secret(passwordEncoder.encode("123123")) //配置访问token的有效期 .accessTokenValiditySeconds(3600) //配置刷新token的有效期 .refreshTokenValiditySeconds(864000) //配置redirect_uri,用于授权成功后跳转 .redirectUris("http://localhost:8081/login", "http://localhost:8082/login") //自动授权配置 .autoApprove(true) //配置申请的权限范围 .scopes("all") /** * 配置grant_type,表示授权类型 * authorization_code: 授权码 * password: 密码 * client_credentials: 客户端 * refresh_token: 更新令牌 */ .authorizedGrantTypes("authorization_code","password","refresh_token"); } }