SpringCloud整合spring security+ oauth2+Redis实现认证授权

avatar
作者
筋斗云
阅读量:2

文章目录

设置通用父工程依赖

在微服务构建中,我们一般用一个父工程来通知管理依赖的各种版本号信息。父工程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配置类主要完成以下配置:

  1. 注入 Redis 连接工厂
  2. 初始化 RedisTokenStore 用于将 token 存储至 Redis
  3. 初始化密码编码器,用 MD5 加密密码
  4. 初始化认证管理对象
  5. 设置放行和认证规则

授权服务配置类

配置完了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客户端中的配置:
image.png
在body中配置请求参数,发起请求后返回如下:
在这里插入图片描述
在Redis中我们也可以看到生成的相关token配置:
在这里插入图片描述

至此,我们完成了认证授权中心的初步搭建。

本文内容到此结束了,
如有收获欢迎点赞👍收藏💖关注✔️,您的鼓励是我最大的动力。
如有错误❌疑问💬欢迎各位指出。
主页共饮一杯无的博客汇总👨‍💻

保持热爱,奔赴下一场山海。🏃🏃🏃

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!