快速学习安全框架 Springsecurity最新版(6.2)--用户授权模块

简介

上一节Springsecurity 用户认证
Springsecurity 拥有强大的认证和授权功能并且非常灵活,,一来说我们都i有以下需求
可以帮助应用程序实现以下两种常见的授权需求:

  • 用户-权限-资源:例如张三的权限是添加用户、查看用户列表,李四的权限是查看用户列表

  • 用户-角色-权限-资源:例如 张三是角色是管理员、李四的角色是普通用户,管理员能做所有操作,普通用户只能查看信息

RBAC权限模型

​ RBAC权限模型(Role-Based Access Control)即:基于角色的权限控制。这是目前最常被开发者使用也是相对易用、通用权限模型 即每个用户对应不同角色,每个角色对应不同功能

用户-角色-权限-资源

RBAC(Role-Based Access Control,基于角色的访问控制)是一种常用的数据库设计方案,它将用户的权限分配和管理与角色相关联。以下是一个基本的RBAC数据库设计方案的示例:

  1. 用户表(User table):包含用户的基本信息,例如用户名、密码和其他身份验证信息。
列名数据类型描述
user_idint用户ID
usernamevarchar用户名
passwordvarchar密码
emailvarchar电子邮件地址
  1. 角色表(Role table):存储所有可能的角色及其描述。
列名数据类型描述
role_idint角色ID
role_namevarchar角色名称
descriptionvarchar角色描述
  1. 权限表(Permission table):定义系统中所有可能的权限。
列名数据类型描述
permission_idint权限ID
permission_namevarchar权限名称
descriptionvarchar权限描述
  1. 用户角色关联表(User-Role table):将用户与角色关联起来。
列名数据类型描述
user_role_idint用户角色关联ID
user_idint用户ID
role_idint角色ID
  1. 角色权限关联表(Role-Permission table):将角色与权限关联起来。
列名数据类型描述
role_permission_idint角色权限关联ID
role_idint角色ID
permission_idint权限ID

在这个设计方案中,用户可以被分配一个或多个角色,而每个角色又可以具有一个或多个权限。通过对用户角色关联和角色权限关联表进行操作,可以实现灵活的权限管理和访问控制。

当用户尝试访问系统资源时,系统可以根据用户的角色和权限决定是否允许访问。这样的设计方案使得权限管理更加简单和可维护,因为只需调整角色和权限的分配即可,而不需要针对每个用户进行单独的设置。其实就是权限或者角色不会直接在用户属性里,而是在多表联查中赋予
比如
来获取用户权限或者角色

<select id="selectPermsByUserId" resultType="java.lang.String">SELECTDISTINCT m.`perms`FROMsys_user_role urLEFT JOIN `sys_role` r ON ur.`role_id` = r.`id`LEFT JOIN `sys_role_menu` rm ON ur.`role_id` = rm.`role_id`LEFT JOIN `sys_menu` m ON m.`id` = rm.`menu_id`WHEREuser_id = #{userid}AND r.`status` = 0AND m.`status` = 0
</select>

基于request的授权

在首页的用户笔记中有项目目录,现在修改其中的俩个接口权限

官方架构
在这里插入图片描述

之前只要携带正确的token 就可以访问
在这里插入图片描述
修改配置文件后

  @Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http.csrf(AbstractHttpConfigurer::disable).addFilterBefore(new JwtAuthenticationTokenFilter(redisCache,jwtUtil), UsernamePasswordAuthenticationFilter.class).sessionManagement(AbstractHttpConfigurer::disable).authorizeHttpRequests(auth -> auth.requestMatchers(HttpMethod.POST,"/auth/login").permitAll() // 对登录接口允许匿名访问.requestMatchers("/user/list").hasAuthority("user:list").requestMatchers("/user/add").hasAuthority("user:add").requestMatchers(HttpMethod.OPTIONS).permitAll()
//                        .requestMatchers("**").permitAll().anyRequest().authenticated()).exceptionHandling(exception -> exception.authenticationEntryPoint(new MyAuthenticationEntryPoint())).headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable));return http.build();}

返回的内容是自己定义的json
在这里插入图片描述
但是响应码确实变成了403 security不允许访问
在这里插入图片描述

实现授权:权限-资源

实现不同权限访问不同接口

配置文件限定接口
@EnableMethodSecurity
@Configuration
@AllArgsConstructor
public class WebSecurityConfig {private final ApplicationEventPublisher applicationEventPublisher;@Autowiredprivate RedisCache redisCache;@Autowiredprivate JwtUtil jwtUtil;
//    从配置文件注入  ioc先扫描配置文件
@ResourceUserMapper userMapper;/*** 这个Bean创建了一个认证管理器对象,它是Spring Security认证的核心组件之一。* 认证管理器负责协调和管理认证流程,并委托给一个或多个认证提供者(在这里,使用了daoAuthenticationProvider)来进行具体的认证操作。* 这里通过创建一个ProviderManager对象,将之前配置的daoAuthenticationProvider添加到认证管理器中。* 还通过setAuthenticationEventPublisher()方法设置了一个事件发布器,用于在认证事件发生时发布相关的事件,* 这里使用了DefaultAuthenticationEventPublisher,并传入了一个applicationEventPublisher对象,可能用于发布认证事件到Spring的事件机制中。* @return*/@Beanpublic AuthenticationManager authenticationManager() {List<AuthenticationProvider> providerList = new ArrayList<>();providerList.add(daoAuthenticationProvider());ProviderManager providerManager = new ProviderManager(providerList);providerManager.setAuthenticationEventPublisher(new DefaultAuthenticationEventPublisher(applicationEventPublisher));return providerManager;}/*** 是Spring Security用于处理基于数据库的用户认证的提供者。* DaoAuthenticationProvider需要一个UserDetailsService对象来获取用户的详细信息进行认证,* 所以通过setUserDetailsService()方法设置了我们之前设置的manager。* @return*/@BeanDaoAuthenticationProvider daoAuthenticationProvider() {DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();daoAuthenticationProvider.setPasswordEncoder( passwordEncoder());daoAuthenticationProvider.setUserDetailsService(new DBUserDetailsManager(userMapper));return daoAuthenticationProvider;}/*** 把默认的密码加密器换成我们自定义的加密器* @return*/@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http.csrf(AbstractHttpConfigurer::disable).addFilterBefore(new JwtAuthenticationTokenFilter(redisCache,jwtUtil), UsernamePasswordAuthenticationFilter.class).sessionManagement(AbstractHttpConfigurer::disable).authorizeHttpRequests(auth -> auth.requestMatchers(HttpMethod.POST,"/auth/login").permitAll() // 对登录接口允许匿名访问.requestMatchers("/user/list").hasAuthority("user:list")//对接口定制限定权限.requestMatchers("/user/add").hasAuthority("user:add").requestMatchers(HttpMethod.OPTIONS).permitAll()
//                        .requestMatchers("**").permitAll()//对所有请求开启授权保护.anyRequest()//已认证的请求会被自动授权.authenticated()).exceptionHandling(//   异常结果处理 1.认证异常处理2.授权异常处理exception -> exception.authenticationEntryPoint(new MyAuthenticationEntryPoint()).accessDeniedHandler((request, response, e)->{//创建结果对象HashMap result = new HashMap();result.put("code", -1);result.put("message", "没有权限");//转换成json字符串String json = JSON.toJSONString(result);//返回响应response.setContentType("application/json;charset=UTF-8");response.getWriter().println(json);})).headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable));return http.build();}}
规范用户类

改造用户类
这里做简单模拟 写一个字段作为权限字段,往往实际开发中是一个json 数组用于转换

/*** @TableName user*/
@TableName(value ="user")
@Data
public class User implements Serializable {private Integer id;private String username;private String password;private Integer enabled;//实际开发中权限字段多半是json 里卖装的权限数组@TableField(exist = false)private List<GrantedAuthority> authorities;private static final long serialVersionUID = 1L;}

security定义的规范用户类

@Datapublic class UserDetail implements UserDetails {private User user;//存储SpringSecurity所需要的权限信息的集合 构造函数时候用于将user的json数组转换为这个权限对象private List<GrantedAuthority> authorities;@Overridepublic List<? extends GrantedAuthority> getAuthorities() {return authorities;}@Overridepublic String getPassword() {return user.getPassword();}@Overridepublic String getUsername() {return user.getUsername();}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}

userdetailsService的实现类

@Component
@Slf4j
@AllArgsConstructor
public class DBUserDetailsManager implements UserDetailsManager, UserDetailsPasswordService {private UserMapper userMapper;
//    这样就可以按照security的规范来使用用户的管理@Overridepublic UserDetails updatePassword(UserDetails user, String newPassword) {return null;}@Overridepublic void createUser(UserDetails userDetails) {
//        在sql中插入信息User user = new User();user.setUsername(userDetails.getUsername());user.setPassword(userDetails.getPassword());user.setEnabled(1);userMapper.insert(user);}@Overridepublic void updateUser(UserDetails user) {}@Overridepublic void deleteUser(String username) {}@Overridepublic void changePassword(String oldPassword, String newPassword) {}@Overridepublic boolean userExists(String username) {return false;}//security底层会根据这个方法来对比用户@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//        这里用户账户是唯一的User user = userMapper.selectOne(Wrappers.<User>lambdaQuery().eq(User::getUsername, username));if (user == null){throw new UsernameNotFoundException("系统用户不存在");}else{
//           1表示可用boolean isenabled = user.getEnabled() == 1;
/*** ,任何非零的整数值都会被视为 true,而 0 会被视为 false。*/log.info("数据库个根据用户名获取用户"+user);//模拟系统权限列表List<GrantedAuthority> authorities = new ArrayList<>();// 写一个静态数据模拟用户数据库中的权限authorities.add(()->"user:list");user.setAuthorities(authorities);UserDetail detail = new UserDetail();detail.setUser(user);detail.setAuthorities(authorities);return detail;}}
}
改造过滤器
@Slf4j
@AllArgsConstructorpublic class JwtAuthenticationTokenFilter extends OncePerRequestFilter {private final RedisCache redisCache;private final   JwtUtil jwtUtil;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {try {String token = request.getHeader("token");if (tokenNotRequired(request.getRequestURI())) {
//                    登录接口直接放行filterChain.doFilter(request, response);return;}if (token == null || token.trim().isEmpty()) {throw new AuthenticationException("需要登录") {};}String username = jwtUtil.getUsernameFromToken(token);String redisKey = "logintoken:" + username;String jsonString = redisCache.getCacheObject(redisKey);if (jsonString == null || jsonString.trim().isEmpty()) {throw new AuthenticationException("用户登录已过期") {};}UserDetail userInfo = JSON.parseObject(jsonString, UserDetail.class);UsernamePasswordAuthenticationToken authenticationToken =new UsernamePasswordAuthenticationToken(userInfo, null, userInfo.getAuthorities());SecurityContextHolder.getContext().setAuthentication(authenticationToken); //设置给上下文对象filterChain.doFilter(request, response);}//对所有抛出的异常进行处理catch (AuthenticationException e) {response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);response.setContentType("application/json; charset=utf-8");response.getWriter().println(JSON.toJSONString(Result.nologin(e.getMessage())));}}private boolean tokenNotRequired(String requestURI) {return "/auth/login".equals(requestURI) || "/auth/info".equals(requestURI);}
}

访问上下文对象

    @GetMapping("/")public Result index() {SecurityContext context = SecurityContextHolder.getContext();//存储认证对象的上下文Authentication authentication = context.getAuthentication();//认证对象String username = authentication.getName();//用户名Object principal =authentication.getPrincipal();//身份Object credentials = authentication.getCredentials();//凭证(脱敏)Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();//权限System.out.println(username);System.out.println(principal);System.out.println(credentials);System.out.println(authorities);HashMap<String, Object> map = new HashMap<>();map.put("认证对象", authentication);map.put("身份信息", principal);map.put("creden", credentials);return Result.success(map);}

此时可以发现权限已经设置好了
在这里插入图片描述

上述的文件操作中,我在loaduserbyusername中只添加了user:list权限,现在再次访问这俩个有权限设置的接口

userl:list 权限
在这里插入图片描述

user:add权限
因为没有该权限 所以触发未授权处理器
在这里插入图片描述

实现用户-角色-资源

修改配置文件接口

     http.csrf(AbstractHttpConfigurer::disable).addFilterBefore(new JwtAuthenticationTokenFilter(redisCache,jwtUtil), UsernamePasswordAuthenticationFilter.class).sessionManagement(AbstractHttpConfigurer::disable).authorizeHttpRequests(auth -> auth.requestMatchers(HttpMethod.POST,"/auth/login").permitAll() // 对登录接口允许匿名访问.requestMatchers("/user/list").hasAuthority("user:list").requestMatchers("/user/add").hasRole("admin").requestMatchers(HttpMethod.OPTIONS).permitAll()
//                        .requestMatchers("**").permitAll().anyRequest().authenticated()).exceptionHandling(exception -> exception.authenticationEntryPoint(new MyAuthenticationEntryPoint())).headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable));return http.build();

该接口

//    动态添加系统授权用户@PostMapping("/add")public String addUser(@RequestBody User user) {
log.info("add user"+user);userService.adduser(user);
return "添加用户";}

此时一个接口需要user:list权限 (查看用户列表) 一个需要当前用户是管理员角色 才能对系统用户进行新增

修改我们定义的用户

@TableName(value ="user")
@Data
public class User implements Serializable {private Integer id;private String username;private String password;private Integer enabled;//实际开发中权限字段多半是json 里卖装的权限数组@TableField(exist = false)private List<GrantedAuthority> authorities;//模拟数据存放的都是用户json数组@TableField(exist = false)public List<String> roles;private static final long serialVersionUID = 1L;}

修改userdetailsService实现类 静态赋予角色(模拟该用户数据中的角色 这里的用户表只有简单的用户密码和账户字段)

@Component
@Slf4j
@AllArgsConstructor
public class DBUserDetailsManager implements UserDetailsManager, UserDetailsPasswordService {private UserMapper userMapper;//    这样就可以按照security的规范来使用用户的管理@Overridepublic UserDetails updatePassword(UserDetails user, String newPassword) {return null;}@Overridepublic void createUser(UserDetails userDetails) {
//        在sql中插入信息User user = new User();user.setUsername(userDetails.getUsername());user.setPassword(userDetails.getPassword());user.setEnabled(1);userMapper.insert(user);}@Overridepublic void updateUser(UserDetails user) {}@Overridepublic void deleteUser(String username) {}@Overridepublic void changePassword(String oldPassword, String newPassword) {}@Overridepublic boolean userExists(String username) {return false;}//security底层会根据这个方法来对比用户@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//        这里用户账户是唯一的User user = userMapper.selectOne(Wrappers.<User>lambdaQuery().eq(User::getUsername, username));if (user == null){throw new UsernameNotFoundException("系统用户不存在");}else{
//           1表示可用boolean isenabled = user.getEnabled() == 1;
/*** ,任何非零的整数值都会被视为 true,而 0 会被视为 false。*/log.info("数据库个根据用户名获取用户"+user);List<String> roles = new ArrayList<>();//模拟数据中的该用户的角色数据//假如该用户有俩个数据roles.add("admin");roles.add("leader");//模拟系统权限列表List<GrantedAuthority> authorities = new ArrayList<>();// 写一个静态数据模拟用户数据库中的权限authorities.add(()->"user:list");roles.forEach(role-> authorities.add(()->"ROLE_"+role));user.setAuthorities(authorities);UserDetail detail = new UserDetail(user);detail.setAuthorities(authorities);return detail;}}
}

关键代码块解析
这里创建一个集合模拟该用户在数据的角色数据,然后和用户数据的添加设置到userdetals的实现类中,这里为什么这么设置角色?为什么角色和权限放在同一字段

ist<String> roles = new ArrayList<>();//模拟数据中的该用户的角色数据//假如该用户有俩个数据roles.add("admin");roles.add("leader");//模拟系统权限列表List<GrantedAuthority> authorities = new ArrayList<>();// 写一个静态数据模拟用户数据库中的权限authorities.add(()->"user:list");roles.forEach(role-> authorities.add(()->"ROLE_"+role));user.setAuthorities(authorities);

打开限制接口的源码hasRole(“admin”)
在这里插入图片描述
发现和权限的区别只有多了一个前缀作为识别而已
在这里插入图片描述
重启项目访问上下文对象 角色本质底层也是权限,只是有一个前缀醉作为识别
在这里插入图片描述

访问限制接口

成功
在这里插入图片描述

已经和认证处理器一样拥有丰富的授权事件处理器

在这里插入图片描述

基于方法的授权

这个是偏向主流的鉴权官网地址->方法安全

配置文件类中添加如下注解 开启方法鉴权 这种开启功能的注解一般放在启动类

@EnableMethodSecurity

userdetailService接口的实现类逻辑依旧是从数据库获取用户和权限等,所以不做修改依旧使用上面的实现类模拟->userdetailservice实现

将配置文件中有关授权的配置删除

@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http.csrf(AbstractHttpConfigurer::disable).addFilterBefore(new JwtAuthenticationTokenFilter(redisCache,jwtUtil), UsernamePasswordAuthenticationFilter.class).sessionManagement(AbstractHttpConfigurer::disable).authorizeHttpRequests(auth -> auth.requestMatchers(HttpMethod.POST,"/auth/login").permitAll() // 对登录接口允许匿名访问.requestMatchers(HttpMethod.OPTIONS).permitAll()
//                        .requestMatchers("**").permitAll().anyRequest().authenticated()).exceptionHandling(exception -> exception.authenticationEntryPoint(new MyAuthenticationEntryPoint())).headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable));return http.build();}

对于需要限定的接口添加PreAuthorize注解

//  限定角色:  用户必须有 ADMIN 角色 并且 用户名是 admin 才能访问此方法@PreAuthorize("hasRole('admin') and authentication.name == 'admim'")@GetMapping("/list")
public List<User> getList(){return userService.list();
}//限定权限: 用户必须有 USER_ADD 权限 才能访问此方法@PreAuthorize("hasAuthority('user:add')")
@PostMapping("/add")
public void add(@RequestBody User user){userService.saveUserDetails(user);
}

由于当前用户名是123 所以也会触发授权失败的处理
在这里插入图片描述
修改回来即可访问成功
在这里插入图片描述

outh2

springsecurity官网配置

OAuth 2.0 是一种开放标准的授权框架,允许用户授权第三方应用访问他们存储在资源所有者(比如一个网站或服务)上的受保护资源,而不需要将身份凭证(比如用户名和密码)直接暴露给第三方应用。OAuth 2.0 的主要设计目标是为了简化客户端开发,并提供更强大的授权机制。OAuth 2.0 不是认证协议,而是一个授权协议。它允许资源所有者在不泄露他们的凭据的情况下委托限定的访问权限。

OAuth 2.0 提供了一种安全的方式,让用户允许第三方应用访问他们在其他网站或服务上的受保护资源,而无需共享他们的用户名和密码。

1、OAuth2简介

1.1、OAuth2是什么

“Auth” 表示 “授权” Authorization

“O” 是 Open 的简称,表示 “开放”

连在一起就表示 “开放授权”,OAuth2是一种开放授权协议。

OAuth2最简向导:The Simplest Guide To OAuth 2.0

1.2、OAuth2的角色

OAuth 2协议包含以下角色:

  1. 资源所有者(Resource Owner):即用户,资源的拥有人,想要通过客户应用访问资源服务器上的资源。
  2. 客户应用(Client):通常是一个Web或者无线应用,它需要访问用户的受保护资源。
  3. 资源服务器(Resource Server):存储受保护资源的服务器或定义了可以访问到资源的API,接收并验证客户端的访问令牌,以决定是否授权访问资源。
  4. 授权服务器(Authorization Server):负责验证资源所有者的身份并向客户端颁发访问令牌。

在这里插入图片描述

1.3、OAuth2的使用场景

开放系统间授权
社交登录

在传统的身份验证中,用户需要提供用户名和密码,还有很多网站登录时,允许使用第三方网站的身份,这称为"第三方登录"。所谓第三方登录,实质就是 OAuth 授权。用户想要登录 A 网站,A 网站让用户提供第三方网站的数据,证明自己的身份。获取第三方网站的身份数据,就需要 OAuth 授权。

在这里插入图片描述

开放API

例如云冲印服务的实现

在这里插入图片描述

现代微服务安全
单块应用安全

在这里插入图片描述

微服务安全

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

企业内部应用认证授权
  • SSO:Single Sign On 单点登录

  • IAM:Identity and Access Management 身份识别与访问管理

1.4、OAuth2的四种授权模式

RFC6749:

RFC 6749 - The OAuth 2.0 Authorization Framework (ietf.org)

阮一峰:

OAuth 2.0 的四种方式 - 阮一峰的网络日志 (ruanyifeng.com)

四种模式:

  • 授权码(authorization-code)
  • 隐藏式(implicit)
  • 密码式(password)
  • 客户端凭证(client credentials)
第一种方式:授权码

授权码(authorization code),指的是第三方应用先申请一个授权码,然后再用该码获取令牌。

这种方式是最常用,最复杂,也是最安全的,它适用于那些有后端的 Web 应用。授权码通过前端传送,令牌则是储存在后端,而且所有与资源服务器的通信都在后端完成。这样的前后端分离,可以避免令牌泄漏。比如常见的jwt

在这里插入图片描述

  • 注册客户应用:客户应用如果想要访问资源服务器需要有凭证,需要在授权服务器上注册客户应用。注册后会获取到一个ClientID和ClientSecrets

在这里插入图片描述

第二种方式:隐藏式

隐藏式(implicit),也叫简化模式,有些 Web 应用是纯前端应用,没有后端。这时就不能用上面的方式了,必须将令牌储存在前端。

RFC 6749 规定了这种方式,允许直接向前端颁发令牌。这种方式没有授权码这个中间步骤,所以称为隐藏式。这种方式把令牌直接传给前端,是很不安全的。因此,只能用于一些安全要求不高的场景,并且令牌的有效期必须非常短,通常就是会话期间(session)有效,浏览器关掉,令牌就失效了。

在这里插入图片描述

https://a.com/callback#token=ACCESS_TOKEN
将访问令牌包含在URL锚点中的好处:锚点在HTTP请求中不会发送到服务器,减少了泄漏令牌的风险。
第三种方式:密码式

密码式(Resource Owner Password Credentials):如果你高度信任某个应用,RFC 6749 也允许用户把用户名和密码,直接告诉该应用。该应用就使用你的密码,申请令牌。
比如一个没有和其他服务对接的应用

这种方式需要用户给出自己的用户名/密码,显然风险很大,因此只适用于其他授权方式都无法采用的情况,而且必须是用户高度信任的应用。

在这里插入图片描述

第四种方式:凭证式

凭证式(client credentials):也叫客户端模式,适用于没有前端的命令行应用,即在命令行下请求令牌。

这种方式给出的令牌,是针对第三方应用的,而不是针对用户的,即有可能多个用户共享同一个令牌。
在这里插入图片描述

1.5、授权类型的选择

在这里插入图片描述

2、Spring中的OAuth2

2.1、相关角色

回顾: OAuth 2中的角色

  1. 资源所有者(Resource Owner)
  2. 客户应用(Client)
  3. 资源服务器(Resource Server)
  4. 授权服务器(Authorization Server)

2.2、Spring中的实现

OAuth2 :: Spring Security

Spring Security

  • 客户应用(OAuth2 Client):OAuth2客户端功能中包含OAuth2 Login
  • 资源服务器(OAuth2 Resource Server)

Spring

  • 授权服务器(Spring Authorization Server):它是在Spring Security之上的一个单独的项目。

2.3、相关依赖

<!-- 资源服务器 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency><!-- 客户应用 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency><!-- 授权服务器 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-oauth2-authorization-server</artifactId>
</dependency>

2.4、授权登录的实现思路

使用OAuth2 Login 比如登录淘宝可以使用微信登陆和支付宝登录

在这里插入图片描述

3、GiuHub社交登录案例

3.1、创建应用

注册客户应用:

登录GitHub,在开发者设置中找到OAuth Apps,创建一个application,为客户应用创建访问GitHub的凭据:

在这里插入图片描述

填写应用信息:默认的重定向URI模板为{baseUrl}/login/oauth2/code/{registrationId}。registrationId是ClientRegistration的唯一标识符。

在这里插入图片描述

获取应用程序id,生成应用程序密钥:

在这里插入图片描述

3.2、创建测试项目

创建一个springboot项目oauth2-login-demo,创建时引入如下依赖

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

示例代码参考:spring-security-samples/servlet/spring-boot/java/oauth2/login at 6.2.x · spring-projects/spring-security-samples (github.com)

3.3、配置OAuth客户端属性

application.yml:

spring:security:oauth2:client:registration:github:client-id: 7807cc3bb1534abce9f2client-secret: 008dc141879134433f4db7f62b693c4a5361771b
#            redirectUri: http://localhost:8200/login/oauth2/code/github

3.4、创建Controller

package com.atguigu.oauthdemo.controller;@Controller
public class IndexController {@GetMapping("/")public String index(Model model,@RegisteredOAuth2AuthorizedClient OAuth2AuthorizedClient authorizedClient,@AuthenticationPrincipal OAuth2User oauth2User) {model.addAttribute("userName", oauth2User.getName());model.addAttribute("clientName", authorizedClient.getClientRegistration().getClientName());model.addAttribute("userAttributes", oauth2User.getAttributes());return "index";}
}

3.5、创建html页面

resources/templates/index.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org" xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head><title>Spring Security - OAuth 2.0 Login</title><meta charset="utf-8" />
</head>
<body>
<div style="float: right" th:fragment="logout" sec:authorize="isAuthenticated()"><div style="float:left"><span style="font-weight:bold">User: </span><span sec:authentication="name"></span></div><div style="float:none">&nbsp;</div><div style="float:right"><form action="#" th:action="@{/logout}" method="post"><input type="submit" value="Logout" /></form></div>
</div>
<h1>OAuth 2.0 Login with Spring Security</h1>
<div>You are successfully logged in <span style="font-weight:bold" th:text="${userName}"></span>via the OAuth 2.0 Client <span style="font-weight:bold" th:text="${clientName}"></span>
</div>
<div>&nbsp;</div>
<div><span style="font-weight:bold">User Attributes:</span><ul><li th:each="userAttribute : ${userAttributes}"><span style="font-weight:bold" th:text="${userAttribute.key}"></span>: <span th:text="${userAttribute.value}"></span></li></ul>
</div>
</body>
</html>

3.6、启动应用程序

  • 启动程序并访问localhost:8080。浏览器将被重定向到默认的自动生成的登录页面,该页面显示了一个用于GitHub登录的链接。
  • 点击GitHub链接,浏览器将被重定向到GitHub进行身份验证。
  • 使用GitHub账户凭据进行身份验证后,用户会看到授权页面,询问用户是否允许或拒绝客户应用访问GitHub上的用户数据。点击允许以授权OAuth客户端访问用户的基本个人资料信息。
  • 此时,OAuth客户端访问GitHub的获取用户信息的接口获取基本个人资料信息,并建立一个已认证的会话。

4、案例分析

4.1、登录流程

  1. A 网站让用户跳转到 GitHub,并携带参数ClientID 以及 Redirection URI。
  2. GitHub 要求用户登录,然后询问用户"A 网站要求获取用户信息的权限,你是否同意?"
  3. 用户同意,GitHub 就会重定向回 A 网站,同时发回一个授权码。
  4. A 网站使用授权码,向 GitHub 请求令牌。
  5. GitHub 返回令牌.
  6. A 网站使用令牌,向 GitHub 请求用户数据。
  7. GitHub返回用户数据
  8. A 网站使用 GitHub用户数据登录

在这里插入图片描述

4.2、CommonOAuth2Provider

CommonOAuth2Provider是一个预定义的通用OAuth2Provider,为一些知名资源服务API提供商(如Google、GitHub、Facebook)预定义了一组默认的属性。

例如,授权URI、令牌URI和用户信息URI通常不经常变化。因此,提供默认值以减少所需的配置。

因此,当我们配置GitHub客户端时,只需要提供client-id和client-secret属性。

GITHUB {public ClientRegistration.Builder getBuilder(String registrationId) {ClientRegistration.Builder builder = this.getBuilder(registrationId, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, //授权回调地址(GitHub向客户应用发送回调请求,并携带授权码)   "{baseUrl}/{action}/oauth2/code/{registrationId}");builder.scope(new String[]{"read:user"});//授权页面builder.authorizationUri("https://github.com/login/oauth/authorize");//客户应用使用授权码,向 GitHub 请求令牌builder.tokenUri("https://github.com/login/oauth/access_token");//客户应用使用令牌向GitHub请求用户数据builder.userInfoUri("https://api.github.com/user");//username属性显示GitHub中获取的哪个属性的信息builder.userNameAttributeName("id");//登录页面超链接的文本builder.clientName("GitHub");return builder;}
},

官方给的是整合github登录的列子 使用性不强,但是其他平台也是使用outh2 api作为俩调登录
在这里插入图片描述
比如我这里登录获取token 返回令牌就是隐藏式的
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/696056.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

康威生命游戏

康威生命游戏 康威生命游戏(Conway’s Game of Life)是康威发明的细胞自动机。 生命游戏有几个简单的规则&#xff1a; 细胞有两种状态&#xff0c;存活或死亡&#xff0c;每个细胞以自身为中心与周围的八格细胞互动。 对于存活的细胞&#xff1a; 当周围的细胞过少(<2)或…

【Linux】:简易实现自动化构建代码make/Makefile

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本期来给大家解读一下有关Linux自动化构建代码make/makefile的使用&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C 语 言 专 栏&#xff1a;C语言&…

Leo赠书活动-18期 《高效使用Redis》

✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a; 赠书活动专栏 ✨特色专栏&#xff1a;…

Ubuntu22部署MySQL5.7详细教程

Ubuntu22部署MySQL5.7详细教程 一、下载MySQL安装包二、安装MySQL三、启动MySQL检查状态登录MySQL 四、开启远程访问功能1、允许其他主机通过root访问数据库2、修改配置文件&#xff0c;允许其他IP通过自定义端口访问 五、使用Navicat连接数据库 默认情况下&#xff0c;Ubuntu2…

Android的ViewModel

前言 在Compose的学习中&#xff0c;我们在可组合函数中使用rememberSaveable​​​​​​​保存应用数据&#xff0c;但这可能意味着将逻辑保留在可组合函数中或附近。随着应用体量不断变大&#xff0c;您应将数据和逻辑从可组合函数中移出。 而在之前的应用架构学习中&…

【测试开发项目】个人博客项目测试报告

文章目录 前言 一、项目背景 二、项目功能 三、测试用例设计 3.1 个人博客项目测试用例设计 3.1.1 博客登录页测试用例设计 3.1.2 博客列表页测试用例设计 3.1.3 博客详情页测试用例设计 3.1.4 博客编辑页测试用…

从数据库中读取文件导出为Excel

使用的库&#xff08;org.apache.poi&#xff09; 在poi包中有Apache提供的各种分类文件&#xff0c;如下 结构功能HSSF读写Microsoft Excel XLS文件XSSF读写Microsoft Excel OOXML XLSX文件HWPF读写Microsoft Word DOC文件HSLF读写Microsoft PowerPoint文件 下面以XSSF为例&…

【STM32学习】——续上:软件SPI读写W25Q64SPI通信外设硬件SPI读写W25Q64

四、软件SPI读写W25Q64 工程思路与I2C类似&#xff0c;MySPI.c是通信底层&#xff0c;主要包括通信引脚封装、初始化、SPI通信的三个拼图&#xff08;起始、终止和交换一个字节&#xff09;&#xff1b;基于此文件建立W25Q64.c&#xff0c;调用MySPI三个拼图&#xff0c;拼接成…

Flutter插件开发指南01: 通道Channel的编写与实现

Flutter插件开发指南01: 通道Channel的编写与实现 视频 https://www.bilibili.com/video/BV1ih4y1E7E3/ 前言 本文将会通过一个加法计算&#xff0c;来实现 Channel 的双向通讯&#xff0c;让大家有个一个体会。 Flutter插件 Flutter插件是Flutter应用程序与原生平台之间的桥…

ES6内置对象 - Set

Set&#xff08;es6提供的一种数据结构&#xff0c;类似数组&#xff0c;是一个集合&#xff0c;可以存储任何类型的元素且唯一、不重复&#xff0c;so,多用于元素去重&#xff09; 如上图&#xff0c;Set数据结构自带一些方法 1.Set对象创建 let a new Set([1,2,3,3,1,2,4,…

linux 系统的目录结构

为什么某些执行程序位于/bin、/sbin、/usr/bin或/usr/sbin目录下&#xff1f;例如&#xff0c;less命令位于/usr/bin目录下。为什么不是/bin、/sbin或/usr/sbin&#xff1f;这些目录之间有什么区别呢&#xff1f; 在这篇文章中&#xff0c;让我们主要讲述一下Linux文件系统结构…

【代码随想录算法训练营Day24】● 回溯法理论基础 ● 77. 组合

文章目录 Day 24 第七章 回溯算法part01理论基础什么是回溯使用原因 & 解决的问题如何理解回溯法 77. 组合思路剪枝代码 Day 24 第七章 回溯算法part01 今日内容&#xff1a; ● 理论基础● 77. 组合 理论基础 其实在讲解二叉树的时候&#xff0c;就给大家介绍过回溯&am…

计算机服务器中了DevicData勒索病毒怎么办?DevicData勒索病毒解密数据恢复

网络技术的发展与更新为企业提供了极大便利&#xff0c;让越来越多的企业走向了正规化、数字化&#xff0c;因此&#xff0c;企业的数据安全也成为了大家关心的主要话题&#xff0c;但网络是一把双刃剑&#xff0c;即便企业做好了安全防护&#xff0c;依旧会给企业的数据安全带…

python(23)——while循环

前言 在Python中&#xff0c;while 循环用于重复执行一段代码块&#xff0c;只要指定的条件保持为真&#xff08;True&#xff09;。一旦条件变为假&#xff08;False&#xff09;&#xff0c;循环就会终止。while 循环通常用于在不知道循环将执行多少次的情况下进行迭代。 w…

2024.02.22作业

1. 将互斥机制的代码实现重新敲一遍 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <time.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <…

世界顶级名校计算机专业学习使用教材汇总

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-IauYk2cGjEyljid0 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…

GIS、RS、VORS模型、CCDM模型、geodetecto、GWR模型集成的生态系统健康的耦合协调分析

详情V&#xff1a;gjt0312765817632教授如何集成多源数据&#xff0c;依托ArcGIS Pro和R语言环境&#xff0c;采用“活力-组织力-恢复力-贡献力”&#xff08;VORS&#xff09;模型定量测算生态系统健康指数&#xff08;EHI&#xff09;&#xff1b;如何从经济城镇化&#xff0…

瑞_Redis_初识Redis(含安装教程)

文章目录 1 初识Redis1.1 认识NoSQL1.1.1 结构化与非结构化1.1.2 关联和非关联1.1.3 查询方式1.1.4 事务1.1.5 总结 1.2 认识Redis1.2.1 介绍1.2.2 特征1.2.3 优势 1.3 安装Redis ★★★1.3.1 Linux安装Redis1.3.1.1 安装Redis依赖 1.3.2 Windows安装Redis1.3.2.1 安装步骤1.3.…

ETL快速拉取物流信息

我国作为世界第一的物流大国&#xff0c;但是在目前的物流信息系统还存在着几大的痛点。主要包括以下几个方面&#xff1a; 数据孤岛&#xff1a;有些物流企业各个部门之间的数据标准不一致&#xff0c;难以实现数据共享和协同&#xff0c;容易导致信息孤岛。 操作繁琐&#x…

数据结构D3作业

1. 2. 按位插入 void insert_pos(seq_p L,datatype num,int pos) { if(LNULL) { printf("入参为空&#xff0c;请检查\n"); return; } if(seq_full(L)1) { printf("表已满&#xff0c;不能插入\n"); …