SpringCloud微服务整合Spring Security进行统一鉴权

1、思想

  • 网关gateway
  • 普通资源微服务member
  • 鉴权微服务auth
    为了做到更灵活的方法级别的鉴权操作,决定将权限控制下放到具体的普通微服务,其实并不需要多配置很多东西。网关只负责转发请求,鉴权则是由auth认证微服务来完成的。
  • 网上很多都是在网关层面进行鉴权,但是这么做不灵活,并不能做到方法级别的鉴权。比如,普通资源微服务中的一些方法并不需要权限,是开放访问的,而有些方法是需要鉴权的。网关层面只能决定某个微服务是否需要鉴权,要么所有方法全做,要么全不做,是缺少灵活度的。

2、步骤

2.1、前言

  • 有一个大坑,记得如果是单机操作多个微服务项目,要给每个微服务添加session cookie name,如下
server:
  port: 8003
  servlet:
    session:
      cookie:
        #防止Cookie冲突,冲突会导致登录验证不通过
        name: OAUTH2-CLIENT-SESSIONID03
  • 一定一定要做这一步。

2.2、关系

  • 网关gateway
  • 普通资源微服务member
  • 鉴权微服务auth

2.3、认证微服务auth

  • 不需要变动,参考我之前系列的博客:https://blog.csdn.net/qq_41076797/article/details/129985272

2.3.1、微服务目录

在这里插入图片描述
其中整合了swagger的两个配置文件和本项目无关,可忽略

2.3.2、引入必要依赖

auth端:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

还有一些必要的组件是放到了common模块,然后在auth端引入common模块就好了。比如有nacos,一些实体类等等。要记得引入common后也要做必要的配置,比如nacos,相关配置可见博客:https://blog.csdn.net/qq_41076797/article/details/128509393、https://blog.csdn.net/qq_41076797/article/details/128508723;这里就不详细说了。

2.3.3、配置用户鉴权实体类LoginUser

  • 用户已经有实体类User了,这里要对它进行封装,从鉴权的角度把User包一层。
  • 这里要实现UserDetails接口,实现其中的必要方法,这也属于固定套路,这样才能用于权限鉴定。
  • 在auth微服务下面开个detail文件夹,创建实体类LoginUser,
package com.lyy.yingwudemo.yingwu_auth.service;

/**
 * @author :lyy
 * @date : 04-06-10:15
 */
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;

/**
 * 登录用户身份权限
 *
 * @author ruoyi
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class LoginUser implements UserDetails
{
   
    private static final long serialVersionUID = 1L;

    /**
     * 扩展字段
     */
    private Long userId;

    /**
     * 默认字段
     */
    private String username;
    private String password;
    private Boolean enabled;
    private Collection<SimpleGrantedAuthority> authorities;


    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
   
        return this.authorities;
    }

    @Override
    public String getPassword() {
   
        return this.password;
    }

    @Override
    public String getUsername() {
   
        return this.username;
    }

    @Override
    public boolean isAccountNonExpired() {
   
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
   
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
   
        return true;
    }

    @Override
    public boolean isEnabled() {
   
        return this.enabled;
    }
}

2.3.4、创建根据用户名获取封装的用户信息的service:UserDetailServiceImpl

  • 这个主要就是定义一个类实现固定的接口UserDetailsService中的固定的方法loadUserByUsername,这个方法就是根据字符串username返回一个UserDetails,因为得让springsecurity直到你要进行鉴权的对象啥样啊!
  • 这里通过feign从用户服务member获取用户信息。
package com.lyy.yingwudemo.yingwu_auth.service;

import com.alibaba.fastjson.TypeReference;
import com.lyy.yingwuDemo.yingwu_common.entity.User;
import com.lyy.yingwuDemo.yingwu_common.utils.R;
import com.lyy.yingwudemo.yingwu_auth.feign.MemberFeignService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;

import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collection;

/**
 * @author :lyy
 * @date : 04-06-13:21
 */
@Service
@Slf4j
public class UserDetailServiceImpl implements UserDetailsService {
   

    @Autowired
    private MemberFeignService memberFeignService;

    /**
     *
     * @param username
     * @return 就是负责构建一个UserDetails,咱们之前构建的实体类LoginUser就实现了UserDetails,所以是符合要求的
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
   
        // 后面从管理端获取用户信息
        R r = memberFeignService.getMemberUsername(username);
        TypeReference<User> typeReference = new TypeReference<User>() {
   };
        User user=r.getData("user",typeReference);
        if(user==null)
            throw new UsernameNotFoundException("用户不存在");
        LoginUser userDetails = loadUser(user);
        if (!userDetails.isEnabled()) {
   
            throw new DisabledException("该账户已被禁用!");
        } else if (!userDetails.isAccountNonLocked()) {
   
            throw new LockedException("该账号已被锁定!");
        } else if (!userDetails.isAccountNonExpired()) {
   
            throw new AccountExpiredException("该账号已过期!");
        }
        return userDetails;
    }

    private LoginUser loadUser(User user) {
   
        Collection<SimpleGrantedAuthority> authorities =new ArrayList<>();
        user.getUserTags().stream().forEach(tag->
            authorities.add(new SimpleGrantedAuthority(tag.equals("1")?"ROLE_ADMIN":"ROLE_USER"))
        );

        LoginUser loginUser = new LoginUser();
        loginUser.setAuthorities(authorities);

        return LoginUser.builder()
                .userId(1L)
                .username(user.getUserName())
                .enabled(user.getEnable())
                .authorities(authorities)
                // 这里的密码就是正确密码,要拿前端传来的和下面的比较
                .password(new BCryptPasswordEncoder().encode(user.getPassWord())).build();
    }
}

2.3.5、如果不想自己设计用户service

不管要不要自己手动设计service,都要通过rpc调用,查询到username对应的那个用户,以及对应的权限
参考以下代码

@Service
@Slf4j
public class SecurityUserDetailService implements UserDetailsService {
   


    @Autowired
    private UserService userService;

    @Autowired
    private PermissionService permissionService;


    @Override
    public UserDetails loadUserByUsername(String username) {
   

        UserEntity user = userService.getUserByUsername(username);
        if (user == null) {
   
            return null;
        }
        //获取权限
        List<PermissionEntry> permissions = permissionService.getPermissionsByUserId(user.getId());
        List<String> codes = permissions.stream().map(PermissionEntry::getCode).collect(Collectors.toList());
        String[] authorities = null;
        if (CollectionUtils.isNotEmpty(codes)) {
   
            authorities = new String[codes.size()];
            codes.toArray(authorities);
        }
        //身份令牌
        String principal = JSON
  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CtrlZ1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值