文章目录
设置通用父工程依赖
在微服务构建中,我们一般用一个父工程来通知管理依赖的各种版本号信息。父工程pom文件如下:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.zjq</groupId> <artifactId>oauth2-demo</artifactId> <packaging>pom</packaging> <version>1.0-SNAPSHOT</version> <modules> <module>commons</module> <module>ms-gateway</module> <module>ms-oauth2-server</module> <module>ms-registry</module> </modules> <!-- 可以集中定义依赖资源的版本信息 --> <properties> <spring-boot-version>2.3.7.RELEASE</spring-boot-version> <spring-cloud-version>Hoxton.SR9</spring-cloud-version> <lombok-version>1.18.16</lombok-version> <commons-lang-version>3.11</commons-lang-version> <mybatis-starter-version>2.1.3</mybatis-starter-version> <mysql-version>8.0.22</mysql-version> <swagger-starter-version>2.1.5-RELEASE</swagger-starter-version> <hutool-version>5.4.7</hutool-version> <guava-version>20.0</guava-version> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <!-- 集中定义依赖,不引入 --> <dependencyManagement> <dependencies> <!-- spring boot 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot-version}</version> <type>pom</type> <scope>import</scope> </dependency> <!-- spring cloud 依赖 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud-version}</version> <type>pom</type> <scope>import</scope> </dependency> <!-- lombok 依赖 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok-version}</version> </dependency> <!-- common-lang3 依赖 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>${commons-lang-version}</version> </dependency> <!-- mybatis 依赖 --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>${mybatis-starter-version}</version> </dependency> <!-- swagger 依赖 --> <dependency> <groupId>com.battcn</groupId> <artifactId>swagger-spring-boot-starter</artifactId> <version>${swagger-starter-version}</version> </dependency> <!-- mysql 依赖 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql-version}</version> </dependency> <!-- hutool 依赖 --> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>${hutool-version}</version> </dependency> <!-- guava 依赖 --> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>${guava-version}</version> </dependency> </dependencies> </dependencyManagement> <!-- 集中定义项目所需插件 --> <build> <pluginManagement> <plugins> <!-- spring boot maven 项目打包插件 --> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </pluginManagement> </build> </project>
构建eureka注册中心
在SpringCloud微服务体系中服务注册中心是一个必要的存在,通过注册中心提供服务的注册和发现。具体细节可以查看我之前的博客,这里不再赘述。我们开始构建一个eureka注册中心,对应的yml配置文件如下:
server: port: 8080 spring: application: # 应用名称 name: ms-registry # 配置 Eureka Server 注册中心 eureka: client: register-with-eureka: false fetch-registry: false service-url: defaultZone: http://localhost:8080/eureka/ logging: pattern: console: '%d{HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n'
对应的项目启动类代码如下:
package com.zjq.msregistry; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; /** * 注册中心 * @author zjq */ //启动 eureka注册中心服务端相关组件 @EnableEurekaServer @SpringBootApplication public class MsRegistryApplication { public static void main(String[] args) { SpringApplication.run(MsRegistryApplication.class, args); } }
至此,一个单体的服务注册中心搭建完成。
构建认证授权服务
上文我们已经完成了注册中心的搭建,接下来我们开始搭建认证授权中心。
配置文件设置
我们同样在父工程下面新建一个子工程,作为认证授权中心的微服务。对应的yml文件和pom文件配置如下:
application.yml
server: port: 8082 # 端口 spring: application: name: ms-oauth2-server # 应用名 # 数据库 datasource: driver-class-name: com.mysql.cj.jdbc.Driver username: root password: 123456 url: jdbc:mysql://127.0.0.1:3306/oauth2?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useUnicode=true&useSSL=false # Redis redis: port: 6379 host: localhost timeout: 3000 database: 1 password: 123456 # swagger swagger: base-package: com.zjq.oauth2 title: 认证服务API接口文档 # Oauth2 client: oauth2: client-id: appId # 客户端标识 ID secret: 123456 # 客户端安全码 # 授权类型 grant_types: - password - refresh_token # token 有效时间,单位秒 token-validity-time: 3600 refresh-token-validity-time: 3600 # 客户端访问范围 scopes: - api - all # 配置 Eureka Server 注册中心 eureka: instance: prefer-ip-address: true instance-id: ${spring.cloud.client.ip-address}:${server.port} client: service-url: defaultZone: http://localhost:8080/eureka/ # Mybatis mybatis: configuration: map-underscore-to-camel-case: true # 开启驼峰映射 # 指标监控健康检查 management: endpoints: web: exposure: include: "*" # 暴露的端点 logging: pattern: console: '%d{HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n'
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>oauth2-demo</artifactId> <groupId>com.zjq</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>ms-oauth2-server</artifactId> <dependencies> <!-- eureka client --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!-- spring web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- spring data redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- mybatis --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> </dependency> <!-- mysql --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!-- spring cloud security --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-security</artifactId> </dependency> <!-- spring cloud oauth2 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> </dependency> <!-- commons 公共项目 --> <dependency> <groupId>com.zjq</groupId> <artifactId>commons</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!-- 自定义的元数据依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> </dependencies> </project>
Security配置类
我们开始搭建Spring Security相关的配置类,具体配置类代码如下:
package com.zjq.oauth2.server.config; import cn.hutool.crypto.digest.DigestUtil; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; 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.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore; import javax.annotation.Resource; /** * Security 配置类 * @author zjq */ @Configuration @EnableWebSecurity public class SecurityConfiguration extends WebSecurityConfigurerAdapter { // 注入 Redis 连接工厂 @Resource private RedisConnectionFactory redisConnectionFactory; /** * 初始化 RedisTokenStore 用于将 token 存储至 Redis * @return */ @Bean public RedisTokenStore redisTokenStore() { RedisTokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory); redisTokenStore.setPrefix("TOKEN:"); // 设置key的层级前缀,方便查询 return redisTokenStore; } // 初始化密码编码器,用 MD5 加密密码 @Bean public PasswordEncoder passwordEncoder() { return new PasswordEncoder() { /** * 加密 * @param rawPassword 原始密码 * @return */ @Override public String encode(CharSequence rawPassword) { return DigestUtil.md5Hex(rawPassword.toString()); } /** * 校验密码 * @param rawPassword 原始密码 * @param encodedPassword 加密密码 * @return */ @Override public boolean matches(CharSequence rawPassword, String encodedPassword) { return DigestUtil.md5Hex(rawPassword.toString()).equals(encodedPassword); } }; } // 初始化认证管理对象 @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } // 放行和认证规则 @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() // 放行的请求 .antMatchers("/oauth/**", "/actuator/**").permitAll() .and() .authorizeRequests() // 其他请求必须认证才能访问 .anyRequest().authenticated(); } }
Security配置类主要完成以下配置:
- 注入 Redis 连接工厂
- 初始化 RedisTokenStore 用于将 token 存储至 Redis
- 初始化密码编码器,用 MD5 加密密码
- 初始化认证管理对象
- 设置放行和认证规则
授权服务配置类
配置完了security配置类后,我们开始编写授权服务配置类,授权服务配置类需要继承AuthorizationServerConfigurerAdapter
并重写对应的方法,tips:idea子类重写父类快捷键是Ctrl+O
,重写后的授权服务配置类如下:
package com.zjq.oauth2.server.config; import com.zjq.commons.model.domain.SignInIdentity; import com.zjq.oauth2.server.service.UserService; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; 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.store.redis.RedisTokenStore; import javax.annotation.Resource; import java.util.LinkedHashMap; /** * 授权服务配置类 * @author zjq */ @Configuration @EnableAuthorizationServer public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter { // RedisTokenSore @Resource private RedisTokenStore redisTokenStore; // 认证管理对象 @Resource private AuthenticationManager authenticationManager; // 密码编码器 @Resource private PasswordEncoder passwordEncoder; // 客户端配置类 @Resource private ClientOAuth2DataConfiguration clientOAuth2DataConfiguration; // 登录校验 @Resource private UserService userService; /** * 配置令牌端点安全约束 * * @param security * @throws Exception */ @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { // 允许访问 token 的公钥,默认 /oauth/token_key 是受保护的 security.tokenKeyAccess("permitAll()") // 允许检查 token 的状态,默认 /oauth/check_token 是受保护的 .checkTokenAccess("permitAll()"); } /** * 客户端配置 - 授权模型 * * @param clients * @throws Exception */ @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory().withClient(clientOAuth2DataConfiguration.getClientId()) // 客户端标识 ID .secret(passwordEncoder.encode(clientOAuth2DataConfiguration.getSecret())) // 客户端安全码 .authorizedGrantTypes(clientOAuth2DataConfiguration.getGrantTypes()) // 授权类型 .accessTokenValiditySeconds(clientOAuth2DataConfiguration.getTokenValidityTime()) // token 有效期 .refreshTokenValiditySeconds(clientOAuth2DataConfiguration.getRefreshTokenValidityTime()) // 刷新 token 的有效期 .scopes(clientOAuth2DataConfiguration.getScopes()); // 客户端访问范围 } /** * 配置授权以及令牌的访问端点和令牌服务 * * @param endpoints * @throws Exception */ @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { // 认证器 endpoints.authenticationManager(authenticationManager) // 具体登录的方法 .userDetailsService(userService) // token 存储的方式:Redis .tokenStore(redisTokenStore); } }
上面用到的客户端配置类如下:
package com.zjq.oauth2.server.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; /** * 客户端配置类 * @author zjq */ @Component @ConfigurationProperties(prefix = "client.oauth2") @Data public class ClientOAuth2DataConfiguration { // 客户端标识 ID private String clientId; // 客户端安全码 private String secret; // 授权类型 private String[] grantTypes; // token有效期 private int tokenValidityTime; /** * refresh-token有效期 */ private int refreshTokenValidityTime; /** * 客户端访问范围 */ private String[] scopes; }
具体登录的方法实现:
登录实现
package com.zjq.oauth2.server.service; import com.zjq.commons.model.domain.SignInIdentity; import com.zjq.commons.model.pojo.Users; import com.zjq.commons.utils.AssertUtil; import com.zjq.oauth2.server.mapper.UsersMapper; import org.springframework.beans.BeanUtils; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import javax.annotation.Resource; /** * 登录校验 * @author zjq */ @Service public class UserService implements UserDetailsService { @Resource private UsersMapper usersMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { AssertUtil.isNotEmpty(username, "请输入用户名"); Users users = usersMapper.selectByAccountInfo(username); if (users == null) { throw new UsernameNotFoundException("用户名或密码错误,请重新输入"); } // 初始化登录认证对象 SignInIdentity signInIdentity = new SignInIdentity(); // 拷贝属性 BeanUtils.copyProperties(users, signInIdentity); return signInIdentity; } }
UsersMapper:
package com.zjq.oauth2.server.mapper; import com.zjq.commons.model.pojo.Users; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; /** * 用户 Mapper * @author zjq */ public interface UsersMapper { /** * * 根据用户名 or 手机号 or 邮箱查询用户信息 * * @param account * @return */ @Select("select id, username, nickname, phone, email, " + "password, avatar_url, roles, is_valid from t_users where " + "(username = #{account} or phone = #{account} or email = #{account})") Users selectByAccountInfo(@Param("account") String account); }
用户实体:
package com.zjq.commons.model.pojo; import com.zjq.commons.model.base.BaseModel; import lombok.Getter; import lombok.Setter; /** * 用户实体类 * * @Author zjq * @Date 2022/10/12 */ @Getter @Setter public class Users extends BaseModel { // 主键 private Integer id; // 用户名 private String username; // 昵称 private String nickname; // 密码 private String password; // 手机号 private String phone; // 邮箱 private String email; // 头像 private String avatarUrl; // 角色 private String roles; }
package com.zjq.commons.model.base; import lombok.Getter; import lombok.Setter; import java.io.Serializable; import java.util.Date; /** * 实体对象公共属性 * * @Author zjq * @Date 2022/10/12 */ @Getter @Setter public class BaseModel implements Serializable { private Integer id; private Date createDate; private Date updateDate; private int isValid; }
到此,我们完成了认证授权服务构建,接下来我们进行测试验证:
测试验证
我们启动注册中心和认证授权微服务。访问注册中心:http://localhost:8080/
可以看到认证授权服务已经注册到注册中心。
接下来我们通过postman访问请求token测试:
Authorization请求头中配置,username和password,对应oauth客户端中的配置:
在body中配置请求参数,发起请求后返回如下:
在Redis中我们也可以看到生成的相关token配置:
至此,我们完成了认证授权中心的初步搭建。
本文内容到此结束了,
如有收获欢迎点赞👍收藏💖关注✔️,您的鼓励是我最大的动力。
如有错误❌疑问💬欢迎各位指出。
主页:共饮一杯无的博客汇总👨💻保持热爱,奔赴下一场山海。🏃🏃🏃