【Nacos】【OAuth2】密码模式-授权服务器与资源服务器分离

密码模式个人理解

在这里插入图片描述

认证(授权)服务器(Authorization Server):用于认证用户,如果客户端认证通过,则发放给其访问资源服务器的令牌
资源服务器(Resource Server):拥有受保护资源,如果请求包含正确的访问令牌,则可以访问资源,可以是提供管理后台、客户端 API 的服务
客户端(Client):可以是浏览器、客户端,也可以是内部服务
资源拥有者(Resource Owner):最终用户,他有访问资源的账号与密码,可以简单把资源拥有者理解成人,他在使用客户端访问资源

(A)用户打开客户端以后,客户端要求用户给予授权
(B)用户同意给予客户端授权
(C)客户端使用上一步获得的授权,向认证服务器申请令牌
(D)认证服务器对客户端进行认证以后,确认无误,同意发放令牌
(E)客户端使用令牌,向资源服务器申请获取资源
(F)资源服务器确认令牌无误,返回资源信息
上述步骤中,(B)是关键,即用户怎样才能给于客户端授权,oauth2有四种授权方式

  1. 授权码模式(authorization code)
  2. 简化模式(implicit)
  3. 密码模式(resource owner password credentials)
  4. 客户端模式(client credentials)

密码模式
用户向客户端提供自己的用户名和密码,客户端使用这些信息,向"服务商提供商"索要授权
(AB)用户向客户端提供用户名和密码(username、password
(C)客户端(携带client_id、client_secret)将用户名和密码发给认证服务器,向后者请求令牌(access_token
(D)认证服务器确认无误后,向客户端提供访问令牌
(E)客户端使用令牌,向资源服务器申请获取资源
(F)资源服务器确认令牌无误(与access_token包含的信息resource_id相匹配),返回资源信息

密码模式开发过程中的参数理解

access_token:令牌,包含有该客户端拥有的resource_ids和用户信息
client_id、client_secret:证明“该客户端是可信的”,确定你是什么客户端,可以访问什么资源服务器(可对应多个resource_id)等,没有无法获取token
username、password:具体的用户,确定你是谁,没有token无法携带用户信息
resource_id:资源服务器的标识

获取token: /oauth/token

  • username:用户名
  • password:用户密码
  • grant_type:授权方式(password)
  • client_id:客户端id
  • client_secret:客户端密码

项目实践

授权服务器与资源服务器在同一个应用中的实现可见:【springboot】OAuth2+SpringSecurity+Mysql——密码模式

概述

项目包含nacos、gateway、sentinel、mybatisplus等,可按自己的需要去除无关的依赖和配置。其中nacos持久化至数据库(详细)、sentinel持久化至nacos(详细

  • **授权服务器应用(nacos-authserver)**功能:认证授权(Authorization Server)、用户管理(相当于Resource Server)。

其中资源服务器Resource Server的用户管理功能也可以单独分离出去至另一个单独的应用。

  • **资源服务器应用(nacos-producer)**功能:提供资源接口(相当于另一个Resource Server)、自定义登录和刷新接口(相当于客户端client

其中客户端client的功能也可以分离出去直接在前端项目实现。

  • 用户角色:ADMIN、ROLE(t_role表)
  • 资源服务器id:auth_server、resource_test(oauth_client_details表)

mysql建表详见https://blog.csdn.net/lorogy/article/details/113695194

授权服务器项目nacos-authserver

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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.6.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>authserver</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>authserver</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>1.8</java.version>
		<spring-cloud.version>Greenwich.SR2</spring-cloud.version>
		<spring-cloud-alibaba.version>2.1.4.RELEASE</spring-cloud-alibaba.version>
		<druid.version>1.1.21</druid.version>
		<mybatis-plus.version>3.2.0</mybatis-plus.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<!--nacos注册-->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
			<version>0.9.0.RELEASE</version>
		</dependency>
		<!--actuator监控、sentinel限流、sentinel规则持久化至nacos-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-actuator</artifactId>
		</dependency>
		<dependency>
			<groupId>com.alibaba.cloud</groupId>
			<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
		</dependency>
		<dependency>
			<groupId>com.alibaba.csp</groupId>
			<artifactId>sentinel-datasource-nacos</artifactId>
		</dependency>
		<!--mysql驱动-->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>8.0.11</version>
		</dependency>

		<!-- mybatisPlus -->
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>${mybatis-plus.version}</version>
		</dependency>
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-generator</artifactId>
			<version>3.2.0</version>
		</dependency>
		<!-- 模板引擎 -->
		<dependency>
			<groupId>org.apache.velocity</groupId>
			<artifactId>velocity-engine-core</artifactId>
			<version>2.1</version>
		</dependency>
		<!-- alibaba的druid数据库连接池 -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
			<version>${druid.version}</version>
		</dependency>
		<!-- lombok -->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<!--fastjson-->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>1.2.62</version>
		</dependency>

		<!-- security-oauth2 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.security.oauth</groupId>
			<artifactId>spring-security-oauth2</artifactId>
			<version>2.3.3.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-jwt</artifactId>
			<version>1.0.10.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.security.oauth.boot</groupId>
			<artifactId>spring-security-oauth2-autoconfigure</artifactId>
			<version>2.1.4.RELEASE</version>
		</dependency>

		<!--swagger相关依赖-->
		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger2</artifactId>
			<exclusions>
				<exclusion>
					<groupId>io.swagger</groupId>
					<artifactId>swagger-annotations</artifactId>
				</exclusion>
				<exclusion>
					<groupId>io.swagger</groupId>
					<artifactId>swagger-models</artifactId>
				</exclusion>
			</exclusions>
			<version>2.9.2</version>
		</dependency>
		<!--swagger防止报错Illegal DefaultValue for parameter type integer-->
		<dependency>
			<groupId>io.swagger</groupId>
			<artifactId>swagger-annotations</artifactId>
			<version>1.5.21</version>
		</dependency>
		<dependency>
			<groupId>io.swagger</groupId>
			<artifactId>swagger-models</artifactId>
			<version>1.5.21</version>
		</dependency>
		<!--增强swagger-bootstrap-ui-->
		<dependency>
			<groupId>com.github.xiaoymin</groupId>
			<artifactId>swagger-bootstrap-ui</artifactId>
			<version>1.9.2</version>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<!--  <scope>runtime</scope>  -->
			<optional>true</optional>
		</dependency>
	</dependencies>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>${spring-cloud.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
			<dependency>
				<groupId>com.alibaba.cloud</groupId>
				<artifactId>spring-cloud-alibaba-dependencies</artifactId>
				<version>${spring-cloud-alibaba.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<version>2.3.0.RELEASE</version>
			</plugin>
		</plugins>
	</build>

</project>

application.yaml
server:
  port: 18891
spring:
  application:
    name: nacos-authserver
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    sentinel:
      transport:
        # 配置Sentinel DashBoard地址
        dashboard: 127.0.0.1:8858
        # 应用与Sentinel控制台交互的端口,应用本地会起一个该端口占用的HttpServer
        # 默认8719端口,假如端口被占用,依次+1,直到找到未被占用端口
        port: 8091
      datasource:
        nacos-producer:
          nacos:
            serverAddr: 127.0.0.1:8848
            dataId: ${spring.application.name}-sentinel-rules
            groupId: SENTINEL_GROUP
            dataType: json
            ruleType: flow
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/oauth?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=CTT
    username: develop
    password: Dev@2021
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
  devtools:
    restart:
      enabled: true
      # 需要热更新的包
      additional-paths: src/main/java,src/main/resources/static
      # 不需要热更新的包
      exclude: src/main/java/**/CodeGenerator.java
management:
  endpoints:
    web:
      exposure:
        include: "*"
mybatis-plus:
  # xml文件,classpath在这儿指/resources
  mapper-locations: classpath*:com/example/authserver/mapper/xml/*.xml
  global-config:
    db-config:
      # 主键类型
      id-type: id_worker
      # 字段策略
      field-strategy: not_empty
      # 数据库类型
      db-type: mysql
  configuration:
    # 配置返回数据库(column下划线命名&&返回java实体是驼峰命名)
    map-underscore-to-camel-case: true
    # call-setters-on-nulls: true
    # 打印SQL
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    cache-enabled: false
logging:
  level:
    root: info
Application
@SpringBootApplication
@EnableDiscoveryClient
@MapperScan(value = "com.example.authserver.mapper")
public class AuthserverApplication {

	public static void main(String[] args) {
		SpringApplication.run(AuthserverApplication.class, args);
	}

}

重写用户认证过程

userService尽量和UserDetailServiceImpl 在同一目录

@Component
public class UserDetailServiceImpl implements UserDetailsService {

    @Autowired
    private UserService userService;

    //重写认证的过程,由AuthenticationManager去调,从数据库中查找用户信息
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 查询用户是否存在
       com.example.authserver.model.User user = userService.findByName(username);
        if (user == null) {
            throw new RuntimeException("username: " + username + " can not be found");
        }
        // 设置用户权限,可在数据库建表,通过用户查询到相应权限(角色),按以下方式加入
        List<GrantedAuthority> authorities = new ArrayList<>();

        ArrayList<String> list = userService.getRolesByUserId(user.getId());
        if (list == null) {
            throw new RuntimeException("username: " + username + " can not be found");
        }

        for (int i = 0; i < list.size(); i++) {
            authorities.add(new SimpleGrantedAuthority(list.get(i)));
        }
        return new User(user.getUsername(), user.getPassword(), authorities);
    }
}
授权服务配置

使用的是jwt token模式,可替换为别的

@Configuration
@EnableAuthorizationServer //注解开启了验证服务器
public class OAuth2AuthServerConfig extends AuthorizationServerConfigurerAdapter {
    @Autowired
    public UserDetailsService userDetailsService;

    @Autowired
    private DataSource dataSource;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        final JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("yjy");
        return converter;
    }

    /**
     * @Description: 配置 token 节点的安全策略
     * @Param: [security]
     * @Return: void
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()");//默认"denyAll()",不允许访问/oauth/check_token;"isAuthenticated()"需要携带auth;"permitAll()"直接访问
    }

    /**
     * @Description: 配置客户端信息, 相当于在认证服务器中注册哪些客户端(包括资源服务器)能访问
     * @Param: [clients]
     * @Return: void
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.jdbc(dataSource); // 设置客户端的配置从数据库中读取,存储在oauth_client_details表
    }

    /**
     * @Description: 配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)
     * @Param: [endpoints]
     * @Return: void
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager) // 开启密码验证
                .accessTokenConverter(accessTokenConverter()) // token生成方式
                .userDetailsService(userDetailsService);

    }
}
认证服务配置
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * 自定义UserDetailsService用来从数据库中根据用户名查询用户信息以及角色信息
     */
    @Autowired
    public UserDetailsService userDetailsService;

    // 防止服务器@Autowired authenticationManager无法注入
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoder());
    }

    /**
     * @Description: 密码编码验证器
     * @Param: []
     * @Return: org.springframework.security.crypto.password.PasswordEncoder
     */
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    /**
     * @Description: 验证配置,第一步认证,请求需走的过滤器链
     * @Param: [http]
     * @Return: void
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .userDetailsService(userDetailsService);
    }
}


授权服务器的功能到此已完成

接下来是资源服务器的用户管理功能:

资源服务配置

访问该资源需要客户端有auth_server的访问权限

@Configuration
@EnableResourceServer
//@EnableGlobalMethodSecurity(prePostEnabled = true)// 单独资源服务器时或许需要
public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter {
    private static final String RESOURCE_ID = "auth_server";

    // 单独资源服务器时才需要
    /*@Bean
    public PasswordEncoder passwordEncoder()
    {
        return new BCryptPasswordEncoder();
    }*/


    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        // 单独资源服务器时才需要试
        /*DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        resources.tokenServices(defaultTokenServices);*/
        resources.resourceId(RESOURCE_ID).stateless(false);
    }

    // 单独资源服务器时才需要
    /*@Bean
    public JwtAccessTokenConverter accessTokenConverter(){
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("yjy");
        return converter;
    }*/

    // 单独资源服务器时才需要
    /*@Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }*/


    /**
     * @Description: 设置资源访问权限需要重写该方法
     * @Param: [http]
     * @Return: void
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/user/**").authenticated();
    }


}
用户管理功能

@PreAuthorize("ADMIN")表示用户需要有权限(角色)ADMIN,上文重写的认证过程loadUserByUsername中将用户的角色role加入了authorities

@RestController
@RequestMapping("/user")
@Api(value = "user", tags = "用户管理")
public class UserController {
    @Autowired
    private UserService userService;

    /**
     * 根据id获取用户
     * @param id
     * @return
     */
    @GetMapping("/{id}")
    @ApiOperation(value="查询用户",notes="需要ADMIN角色用户")
    @PreAuthorize("ADMIN")
    public JsonResult getUser(@PathVariable("id") Long id){
        return JsonResult.ok(userService.getUserById(id));
    }

    /**
     * 获取登录用户相关信息
     * @return
     */
    @GetMapping("/userinfo")
    @ApiOperation(value="获取登录用户相关信息")
    public JsonResult getUserInfo(){
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        Long id= userService.findByName(authentication.getName()).getId();
        return JsonResult.ok(userService.getUserInfoById(id));
    }

    /**
     * 分页获取所有用户列表
     * @param pageNum
     * @param pageSize
     * @return
     */
    @GetMapping("/list")
    @PreAuthorize("ADMIN")
    @ApiOperation(value="查询所有用户",notes="需要ADMIN角色用户")
    public JsonResult list(@RequestParam Integer pageNum, @RequestParam Integer pageSize){
        PageEntity pageEntity=new PageEntity();
        pageEntity.setPageNum(pageNum);
        pageEntity.setPageSize(pageSize);
        return JsonResult.ok(userService.selectAllUser(pageEntity));
    }

    /**
     * 增加用户
     * @param user
     * @return
     */
    @PostMapping
    @ApiOperation(value="增加用户",notes="用户名和密码必填")
    public JsonResult addUser(@RequestBody User user){
        return JsonResult.ok(userService.addUser(user));
    }

    /**
     * 修改用户
     * @param user
     * @return
     */
    @PutMapping
    @ApiOperation(value="修改用户")
    public JsonResult editUser(@RequestBody User user){
        return JsonResult.ok(userService.updateUser(user));
    }

    /**
     * 删除用户
     * @param id
     * @return
     */
    @DeleteMapping("/{id}")
    @PreAuthorize("ADMIN")
    @ApiOperation(value="删除用户",notes="需要ADMIN角色用户")
    public JsonResult delUser(@PathVariable("id") Long id){
        return JsonResult.ok(userService.deleteUser(id));
    }
}

资源服务器项目nacos-producer

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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.6.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>producer</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>producer</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>1.8</java.version>
		<spring-cloud.version>Greenwich.SR2</spring-cloud.version>
		<spring-cloud-alibaba.version>2.1.4.RELEASE</spring-cloud-alibaba.version>
		<druid.version>1.1.21</druid.version>
		<mybatis-plus.version>3.2.0</mybatis-plus.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<!--nacos注册-->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
			<version>0.9.0.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-openfeign</artifactId>
		</dependency>
		<!--actuator监控、sentinel限流、sentinel规则持久化至nacos-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-actuator</artifactId>
		</dependency>
		<dependency>
			<groupId>com.alibaba.cloud</groupId>
			<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
		</dependency>
		<dependency>
			<groupId>com.alibaba.csp</groupId>
			<artifactId>sentinel-datasource-nacos</artifactId>
		</dependency>

		<!--mysql驱动-->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>8.0.11</version>
		</dependency>
		<!-- mybatisPlus -->
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>${mybatis-plus.version}</version>
		</dependency>
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-generator</artifactId>
			<version>3.2.0</version>
		</dependency>
		<!-- 模板引擎 -->
		<dependency>
			<groupId>org.apache.velocity</groupId>
			<artifactId>velocity-engine-core</artifactId>
			<version>2.1</version>
		</dependency>
		<!-- alibaba的druid数据库连接池 -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
			<version>${druid.version}</version>
		</dependency>
		<!-- lombok -->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<!--fastjson-->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>1.2.62</version>
		</dependency>

		<!-- security-oauth2 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.security.oauth</groupId>
			<artifactId>spring-security-oauth2</artifactId>
			<version>2.3.3.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-jwt</artifactId>
			<version>1.0.10.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.security.oauth.boot</groupId>
			<artifactId>spring-security-oauth2-autoconfigure</artifactId>
			<version>2.1.4.RELEASE</version>
		</dependency>

		<!--swagger相关依赖-->
		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger2</artifactId>
			<exclusions>
				<exclusion>
					<groupId>io.swagger</groupId>
					<artifactId>swagger-annotations</artifactId>
				</exclusion>
				<exclusion>
					<groupId>io.swagger</groupId>
					<artifactId>swagger-models</artifactId>
				</exclusion>
			</exclusions>
			<version>2.9.2</version>
		</dependency>
		<!--swagger防止报错Illegal DefaultValue for parameter type integer-->
		<dependency>
			<groupId>io.swagger</groupId>
			<artifactId>swagger-annotations</artifactId>
			<version>1.5.21</version>
		</dependency>
		<dependency>
			<groupId>io.swagger</groupId>
			<artifactId>swagger-models</artifactId>
			<version>1.5.21</version>
		</dependency>
		<!--增强swagger-bootstrap-ui-->
		<dependency>
			<groupId>com.github.xiaoymin</groupId>
			<artifactId>swagger-bootstrap-ui</artifactId>
			<version>1.9.2</version>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<!--  <scope>runtime</scope>  -->
			<optional>true</optional>
		</dependency>
	</dependencies>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>${spring-cloud.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
			<dependency>
				<groupId>com.alibaba.cloud</groupId>
				<artifactId>spring-cloud-alibaba-dependencies</artifactId>
				<version>${spring-cloud-alibaba.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<version>2.3.0.RELEASE</version>
			</plugin>
		</plugins>
	</build>

</project>

application.yaml

端口18890的uri是gateway网关

server:
  port: 18892
spring:
  application:
    name: nacos-producer
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    sentinel:
      transport:
        # 配置Sentinel DashBoard地址
        dashboard: 127.0.0.1:8858
        # 应用与Sentinel控制台交互的端口,应用本地会起一个该端口占用的HttpServer
        # 默认8719端口,假如端口被占用,依次+1,直到找到未被占用端口
        port: 8091
      datasource:
        nacos-producer:
          nacos:
            serverAddr: 127.0.0.1:8848
            dataId: ${spring.application.name}-sentinel-rules
            groupId: SENTINEL_GROUP
            dataType: json
            ruleType: flow
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=CTT
    username: develop
    password: Dev@2021
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
  devtools:
    restart:
      enabled: true
      # 需要热更新的包
      additional-paths: src/main/java,src/main/resources/static
      # 不需要热更新的包
      exclude: src/main/java/**/CodeGenerator.java
management:
  endpoints:
    web:
      exposure:
        include: "*"
mybatis-plus:
  # xml文件,classpath在这儿指/resources
  mapper-locations: classpath*:com/example/producer/mapper/xml/*.xml
  global-config:
    db-config:
      # 主键类型
      id-type: id_worker
      # 字段策略
      field-strategy: not_empty
      # 数据库类型
      db-type: mysql
  configuration:
    # 配置返回数据库(column下划线命名&&返回java实体是驼峰命名)
    map-underscore-to-camel-case: true
    # call-setters-on-nulls: true
    # 打印SQL
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    cache-enabled: false
security:
  oauth2:
    # 对应 OAuth2ClientProperties 类
    #客户端认证,这里配置相当于该资源服务器同时也是一个客户端,可以这里不配置在前端配置
    client:
      client-id: test_client
      client-secret: user
    #对应 ResourceServerProperties 类
    #校验访问令牌的有效性
    resource:
      user-info-uri: http:/127.0.0.1:18890/nacos-authserver/oauth/check_token
    #自定义,获取访问令牌,用于实现/login接口
    access-token-uri: http://127.0.0.1:18890/nacos-authserver/oauth/token
logging:
  level:
    root: info
Application

@EnableFeignClients是为了远程调用nacos-authserver用户管理的接口

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@MapperScan(value = "com.example.producer.mapper")
public class ProducerApplication {

	public static void main(String[] args) {
		SpringApplication.run(ProducerApplication.class, args);
	}

}
资源服务配置

访问该资源需要客户端有resource_test的访问权限

@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter {
    private static final String RESOURCE_ID = "resource_test";

    // 单独资源服务器时或许需要
    @Bean
    public PasswordEncoder passwordEncoder()
    {
        return new BCryptPasswordEncoder();
    }


    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        // 单独资源服务器时或许需要
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        resources.tokenServices(defaultTokenServices);
        
        resources.resourceId(RESOURCE_ID).stateless(false);
    }

    // 单独资源服务器时或许需要
    @Bean
    public JwtAccessTokenConverter accessTokenConverter(){
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("yjy");
        return converter;
    }

    // 单独资源服务器时或许需要
    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }


    /**
     * @Description: 设置资源访问权限需要重写该方法
     * @Param: [http]
     * @Return: void
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/login").permitAll()
                .antMatchers("/refresh").permitAll()
                .antMatchers("/user/userinfo").permitAll()
                .antMatchers("/test/**").authenticated();
    }
}
测试接口

@PreAuthorize("hasRole('ROLE_ADMIN')")

@RestController
@RequestMapping(value = "/test")
@Api(value = "test", tags = "测试接口")
public class HelloController {
    @GetMapping("hello")
    public String hello(@RequestParam(defaultValue = "producer",required = false) String name){
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        return "hello "+authentication.getName()+","+name+" is ready";
    }

    @GetMapping("test1")
    public String test1(){
        return "producer test1";
    }

    @GetMapping("test2")
    public String test2(){
        return "producer test2";
    }

    @GetMapping("/project")
    @PreAuthorize("hasRole('ROLE_ADMIN')")  //具有此角色才可以访问
    public String project(){
        return "This is my project";
    }
}
资源服务器已经完成

使用从授权服务器获取的令牌access_token即可访问该资源

接下来是将该资源服务器同时作为客户端,自定义/login/refresh接口,以及远程调用用户信息接口,目的是前端应用用户只需要账密登录,不存储client_id等相关客户端信息

自定义/login/refresh接口
/**
 * 自定义登录接口和更新接口
 * 此处相当于一个oauth的客户端,调用授权服务器 /oauth/token,使用密码模式获取访问令牌
 */
@RestController
@RequestMapping("/")
public class LoginController {
    @Autowired
    private OAuth2ClientProperties oAuth2ClientProperties;

    @Value("${security.oauth2.access-token-uri}")
    private String accessTokenUri;

    @PostMapping("login")
    public OAuth2AccessToken login(@RequestParam("username") String username,
                                   @RequestParam("password") String password){
        // 创建 ResourceOwnerPasswordResourceDetails 对象,填写密码模式授权需要的请求参数
        ResourceOwnerPasswordResourceDetails resourceDetails=new ResourceOwnerPasswordResourceDetails();
        resourceDetails.setAccessTokenUri(accessTokenUri);
        resourceDetails.setClientId(oAuth2ClientProperties.getClientId());
        resourceDetails.setClientSecret(oAuth2ClientProperties.getClientSecret());
        resourceDetails.setUsername(username);
        resourceDetails.setPassword(password);
        // 创建 OAuth2RestTemplate 对象,是 Spring Security OAuth 封装的工具类,用于请求授权服务器
        OAuth2RestTemplate restTemplate=new OAuth2RestTemplate(resourceDetails);
        // 将 ResourceOwnerPasswordAccessTokenProvider 设置到其中,表示使用密码模式授权
        restTemplate.setAccessTokenProvider(new ResourceOwnerPasswordAccessTokenProvider());
        // 获取访问令牌
        return restTemplate.getAccessToken();

    }

    @PostMapping("refresh")
    public Object refresh(@RequestParam("refresh_token") String refreshToken){
        RestTemplate restTemplate=new RestTemplate();
        HttpHeaders headers=new HttpHeaders();
        headers.setBasicAuth(oAuth2ClientProperties.getClientId(),oAuth2ClientProperties.getClientSecret());
        String url=accessTokenUri+"?grant_type=refresh_token&refresh_token="+refreshToken;
        HttpEntity<HashMap<String, Object>> request = new HttpEntity<>(headers);
        JSONObject jsonObject=restTemplate.postForObject(url,request,JSONObject.class);
        return jsonObject;
    }
}
Feign远程调用

也可通过Feign远程调用nacos-authserver服务的接口,直接调用会因未携带令牌信息报错401,具体调用方法可见https://blog.csdn.net/lorogy/article/details/117712001

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值