赞
踩
SpringCloud 大型系列课程正在制作中,欢迎大家关注与提意见。
程序员每天的CV 与 板砖,也要知其所以然,本系列课程可以帮助初学者学习 SpringBooot 项目开发 与 SpringCloud 微服务系列项目开发
本文章是系列文章中的一篇
本文章实现的是 auth-api 生成令牌的功能
创建方式在这里有详细说明 SpringBoot项目创建【SpringBoot系列1】
然后在 SpringCloud 项目的父 pom.xml 中添加 module
然后在 auth-api 服务中添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
application.yml 配置如下
server:
port: 7001
spring:
application:
name: '@project.name@'
cloud:
nacos:
server-addr: localhost:8848 # nacos地址
创建一个测试 Controller
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author 早起的年轻人 * @version 1.0 **/ @RestController @RequestMapping("/test/auth") public class AuthTestController { @RequestMapping(value = "/index") public String tedtIndex() { return " 测试 auth 访问 "; } }
启动 auth-api 服务 7001 端口,然后浏览器中访问测试接口
访问会被拦截至一个登录页面
添加 Security 的配置如下:
import org.springframework.context.annotation.Bean; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 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.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.provisioning.InMemoryUserDetailsManager; /** * @author 早起的年轻人 * @description 安全管理配置 */ @EnableWebSecurity @EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { //提供用户信息,这里没有从数据库查询用户信息,在内存中模拟 @Bean public UserDetailsService userDetailsService(){ InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager(); inMemoryUserDetailsManager.createUser(User.withUsername("zs").password("123").authorities("admin").build()); return inMemoryUserDetailsManager; } //密码编码器:不加密 @Bean public PasswordEncoder passwordEncoder(){ return NoOpPasswordEncoder.getInstance(); } //授权规则配置 @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() //授权配置 .antMatchers("/login").permitAll() //登录路径放行 .anyRequest().authenticated() //其他路径都要认证之后才能访问 .and().formLogin() //允许表单登录 .successForwardUrl("/loginSuccess") // 设置登陆成功页 .and().logout().permitAll() //登出路径放行 .and().csrf().disable(); //关闭跨域伪造检查 } }
然后重启服务,在登录页面 输入这里配置的用户名 zs 与密码 123 登录成功后就可以正常访问。
修改 WebSecurityConfig 配置文件如下:
import org.springframework.context.annotation.Bean; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 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.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.provisioning.InMemoryUserDetailsManager; /** * @author 早起的年轻人 * @description 安全管理配置 */ @EnableWebSecurity @EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { //提供用户信息,这里没有从数据库查询用户信息,在内存中模拟 // @Bean // public UserDetailsService userDetailsService(){ // InMemoryUserDetailsManager inMemoryUserDetailsManager = // new InMemoryUserDetailsManager(); // inMemoryUserDetailsManager.createUser(User.withUsername("zs").password("123").authorities("admin").build()); // return inMemoryUserDetailsManager; // } @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } //密码编码器:不加密 @Bean public PasswordEncoder passwordEncoder(){ return NoOpPasswordEncoder.getInstance(); } //授权规则配置 @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() //授权配置 .antMatchers("/login").permitAll() //登录路径放行 .anyRequest().authenticated() //其他路径都要认证之后才能访问 .and().formLogin() //允许表单登录 .successForwardUrl("/loginSuccess") // 设置登陆成功页 .and().logout().permitAll() //登出路径放行 .and().csrf().disable(); //关闭跨域伪造检查 } }
然后自定义一个 UserDetailsService 的实现类来查询登录用户的信息
@Slf4j @Component public class UserServiceImpl implements UserDetailsService { /** * 查询用户服务的 Feign */ @Resource private FeignUserClient feignUserClient; @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { //根据用户ID来查询用户信息 UserInfo userInfo = feignUserClient.queryByUserName(s); if (userInfo==null) { return null; } //定义权限 String[] authorities = {"admin"}; //用户的密码 String password = userInfo.getPassword(); //扩展存储用户信息 Map<String,Object> map = new HashMap<>(); map.put("userName",s); map.put("userId",userInfo.getId()); //转JSON String jsonString = JSON.toJSONString(map); //构建 UserDetails userDetails = User.withUsername(jsonString) .password(password).authorities(authorities).build(); return userDetails; } }
我这里边调用了 FeignUserClient 来调用用户服务查询用户详情,大家也可以修改成自己的查询数据库用户方法。
这里重新构建了 UserDetails ,相当于是把用户的密码校验放给了 Spring Security 去做。
如下图所示,访问 oauth/token 接口,输入 用户名与密码来获取 access_token 。
实现密码模式认证,需要配置 AuthenticationManager 与 PasswordEncoder ,修改 WebSecurityConfig 如下:
import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 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; /** * @author 早起的年轻人 * @description 安全管理配置 */ @EnableWebSecurity @EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) @Slf4j public class WebSecurityConfig extends WebSecurityConfigurerAdapter { //提供用户信息,这里没有从数据库查询用户信息,在内存中模拟 // @Bean // public UserDetailsService userDetailsService(){ // InMemoryUserDetailsManager inMemoryUserDetailsManager = // new InMemoryUserDetailsManager(); // inMemoryUserDetailsManager.createUser(User.withUsername("zs").password("123").authorities("admin").build()); // return inMemoryUserDetailsManager; // } @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } //密码编码器:不加密 @Bean public PasswordEncoder passwordEncoder() { // return NoOpPasswordEncoder.getInstance(); return new BCryptPasswordEncoder(); } //授权规则配置 @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() //授权配置 .antMatchers("/oauth/**", "/login/**","/logout/**") .permitAll() //登录路径放行 .anyRequest().authenticated() //其他路径都要认证之后才能访问 .and().formLogin() //允许表单登录 .and().logout().permitAll() //登出路径放行 .and().csrf().disable(); //关闭跨域伪造检查 } }
oauth2 的 授权服务器配置
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; 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.AuthorizationServerTokenServices; import javax.annotation.Resource; /** * @author 早起的年轻人 * @description 授权服务器配置 */ @Configuration @EnableAuthorizationServer public class AuthorizationServer extends AuthorizationServerConfigurerAdapter { @Resource(name = "authorizationServerTokenServicesCustom") private AuthorizationServerTokenServices authorizationServerTokenServices; @Autowired private AuthenticationManager authenticationManager; //客户端详情服务 @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory()// 使用in-memory存储 .withClient("XcWebApp")// client_id .secret(new BCryptPasswordEncoder().encode("XcWebApp"))//客户端密钥 .resourceIds("all")//资源列表 .authorizedGrantTypes("password", "refresh_token")// 该client允许的授权类型authorization_code,password,refresh_token,implicit,client_credentials .scopes("all")// 允许的授权范围 .autoApprove(false);//false跳转到授权页面 } //令牌端点的访问配置 @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) { endpoints .authenticationManager(authenticationManager)//认证管理器 .tokenServices(authorizationServerTokenServices)//令牌管理服务 .allowedTokenEndpointRequestMethods(HttpMethod.POST); } //令牌端点的安全配置 @Override public void configure(AuthorizationServerSecurityConfigurer security) { security .tokenKeyAccess("permitAll()") //oauth/token_key是公开 .checkTokenAccess("permitAll()") //oauth/check_token公开 .allowFormAuthenticationForClients() //表单认证(申请令牌) ; } }
然后需要配置一下 TokenStore 的生成策略
/** * @author 早起的年轻人 * @version 1.0 **/ @Configuration public class TokenStoreConfig { private String SIGNING_KEY = "mq123"; @Autowired TokenStore tokenStore; @Bean public TokenStore tokenStore() { //使用内存存储令牌(普通令牌) return new InMemoryTokenStore(); } //令牌管理服务 @Bean(name = "authorizationServerTokenServicesCustom") public AuthorizationServerTokenServices tokenService() { DefaultTokenServices service = new DefaultTokenServices(); service.setSupportRefreshToken(true);//支持刷新令牌 service.setTokenStore(tokenStore);//令牌存储策略 service.setAccessTokenValiditySeconds(7200); // 令牌默认有效期2小时 service.setRefreshTokenValiditySeconds(259200); // 刷新令牌默认有效期3天 return service; } }
postman 访问 http://localhost:7001/oauth/token
需要注意 认证模式这里的配置 username 与 password 对应上述配置中的
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()//基于内存配置
//客户端ID
.withClient("XcWebApp")
//密钥
.secret(new BCryptPasswordEncoder().encode("XcWebApp"))
...
}
然后请求参数
获取 access_token 成功后 ,可以调用 /oauth/check_token 接口校验
可以刷新 token
http://localhost:7001/oauth/token
参数类型固定
修改 TokenStoreConfig 配置如下:
@Configuration public class TokenStoreConfig { private String SIGNING_KEY = "qwert.123456"; @Autowired TokenStore tokenStore; // @Bean // public TokenStore tokenStore() { // //使用内存存储令牌(普通令牌) // return new InMemoryTokenStore(); // } @Bean public TokenStore tokenStore() { return new JwtTokenStore(accessTokenConverter()); } @Autowired private JwtAccessTokenConverter accessTokenConverter; @Bean public JwtAccessTokenConverter accessTokenConverter() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); converter.setSigningKey(SIGNING_KEY); return converter; } //令牌管理服务 @Bean(name = "authorizationServerTokenServicesCustom") public AuthorizationServerTokenServices tokenService() { DefaultTokenServices service = new DefaultTokenServices(); service.setSupportRefreshToken(true);//支持刷新令牌 service.setTokenStore(tokenStore);//令牌存储策略 TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain(); tokenEnhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter)); service.setTokenEnhancer(tokenEnhancerChain); service.setAccessTokenValiditySeconds(7200); // 令牌默认有效期2小时 service.setRefreshTokenValiditySeconds(259200); // 刷新令牌默认有效期3天 return service; } }
再次访问获取 token 接口,可以发现 access_token 值有改变
上述内容中,生成的令牌保存在内存里,服务一重启就失效了,所以在实际应用开发中,一般将令牌保存在 Redis 中或者数据库中,本项目中是保存在 Redis 中,认证服务中添加redis 的依赖如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
然后 application.yml 中添加 redis 配置如下:
server:
port: 7001
spring:
application:
name: '@project.name@'
cloud:
nacos:
server-addr: localhost:8848 # nacos地址
redis:
database: 0 # Redis数据库索引(默认为0)
host: localhost # Redis服务器地址
port: 6379 # Redis服务器连接端口
password: 12345678 # Redis服务器连接密码(默认为空)
然后添加 RedisTokenStore 的配置如下
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.security.oauth2.provider.token.TokenStore; import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore; @Configuration public class RedisConfig { @Autowired private RedisConnectionFactory redisConnectionFactory; @Bean public TokenStore redisTokenStore(){ return new RedisTokenStore(redisConnectionFactory); } }
再重启服务,再次获取 token 发现生成的token保存在redis中
本项目源码 https://gitee.com/android.long/spring-cloud-biglead/tree/master/biglead-api-06-auth
如果有兴趣可以关注一下公众号 biglead ,每周都会有 java、Flutter、小程序、js 、英语相关的内容分享
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。