spring boot项目整合spring security权限认证

news2024/11/16 4:43:19

一、准备一个spring boot项目

1、引入基础依赖

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

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

		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
			<version>1.1.13</version>
		</dependency>

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.6</version>
		</dependency>

		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>3.3.1</version>
		</dependency>
	</dependencies>

2、项目配置文件,连接数据库

server:
  port: 80
  servlet:
    context-path: /

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/security?serverTimezone=Asia/Shanghai
    username: root
    password: zzybzb

3、一个简单接口

package com.pzz.controller;

import com.pzz.mapper.XcUserMapper;
import com.pzz.po.XcUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
public class LoginController {

    @Autowired
    XcUserMapper userMapper;

    @RequestMapping("/login-success")
    public String loginSuccess() {

        return "登录成功";
    }

    @RequestMapping("/r/r1")
    public String r1() {
        return "访问r1资源";
    }

    @RequestMapping("/r/r2")
    public String r2() {
        return "访问r2资源";
    }
    
}

4、测试接口能跑

在这里插入图片描述

二、整合spring security

1、引入依赖配置

		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-security</artifactId>
			<version>2.1.3.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-oauth2</artifactId>
			<version>2.1.3.RELEASE</version>
		</dependency>

2、启动,再次访问需要登录

这里的admin无法登录
在这里插入图片描述

3、添加配置文件

  • 用户信息
    在内存配置两个用户:zhangsan、lisi
    zhangsan用户拥有的权限为p1
    lisi用户拥有的权限为p2
  • 密码方式
    暂时采用明文方式
  • 安全拦截机制
    /r/**开头的请求需要认证
package com.pzz.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

/**
 * @description 安全管理配置
 */
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
    //配置用户信息服务
    @Bean
    public UserDetailsService userDetailsService() {
        //这里配置用户信息,这里暂时使用这种方式将用户存储在内存中
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        //在内存创建zhangsan,密码123,分配权限p1
        manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
        //在内存创建lisi,密码456,分配权限p2
        manager.createUser(User.withUsername("lisi").password("456").authorities("p2").build());
        return manager;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
//        //密码为明文方式
        return NoOpPasswordEncoder.getInstance();
//        return new BCryptPasswordEncoder();
    }

    //配置安全拦截机制
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/r/**").authenticated()//访问/r开始的请求需要认证通过
                .anyRequest().permitAll()//其它请求全部放行
                .and()
                .formLogin().successForwardUrl("/login-success");//登录成功跳转到/login-success

        http.logout().logoutUrl("/logout");//退出地址

    }

}

4、访问测试

  • 只拦截/r/**路径下的请求,所以login-success没问题
    在这里插入图片描述
  • 访问/r/r1需要登录在这里插入图片描述
  • 输入配置文件设置的用户名密码登录
    在这里插入图片描述在这里插入图片描述
  • 再次访问,不需要登录
    在这里插入图片描述

5、测试退出

  • 配置文件配置了退出的路径

在这里插入图片描述

  • 退出登录,并再次请求/r/r1

在这里插入图片描述
在这里插入图片描述

6、授权测试

用户认证通过去访问系统资源时spring security进行授权控制,判断用户是否有该资源的访问权限,如果有则继续访问,如果没有则拒绝访问。

6.1、配置用户拥有哪些权限

张三的权限是p1
李四的权限是p2
在这里插入图片描述

6.2、指定资源与权限的关系

    @RequestMapping("/r/r1")
    @PreAuthorize("hasAuthority('p1')")//拥有p1权限方可访问
    public String r1() {
        return "访问r1资源";
    }


    @RequestMapping("/r/r2")
    @PreAuthorize("hasAuthority('p2')")//拥有p2权限方可访问
    public String r2() {
        return "访问r2资源";
    }

6.3、重启,访问测试

  • 张三登录,分别访问/r/r1和/r/r2
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • lisi登录,分别访问/r/r1和/r/r2
    在这里插入图片描述在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

7、工作原理

7.1、工作原理

pring Security所解决的问题就是安全访问控制,而安全访问控制功能其实就是对所有进入系统的请求进行拦截,校验每个请求是否能够访问它所期望的资源。根据前边知识的学习,可以通过Filter或AOP等技术来实现,Spring Security对Web资源的保护是靠Filter实现的,所以从这个Filter来入手,逐步深入Spring Security原理。

当初始化Spring Security时,会创建一个名为SpringSecurityFilterChain的Servlet过滤器,类型为 org.springframework.security.web.FilterChainProxy,它实现了javax.servlet.Filter,因此外部的请求会经过此类,下图是Spring Security过虑器链结构图:
在这里插入图片描述

FilterChainProxy是一个代理,真正起作用的是FilterChainProxy中SecurityFilterChain所包含的各个Filter,同时这些Filter作为Bean被Spring管理,它们是Spring Security核心,各有各的职责,但他们并不直接处理用户的认证,也不直接处理用户的授权,而是把它们交给了认证管理器(AuthenticationManager)和决策管理器(AccessDecisionManager)进行处理。

spring Security功能的实现主要是由一系列过滤器链相互配合完成。

在这里插入图片描述

下面介绍过滤器链中主要的几个过滤器及其作用:

  • SecurityContextPersistenceFilter
    这个Filter是整个拦截过程的入口和出口(也就是第一个和最后一个拦截器),会在请求开始时从配置好的 SecurityContextRepository 中获取 SecurityContext,然后把它设置给 SecurityContextHolder。
    在请求完成后将 SecurityContextHolder 持有的 SecurityContext 再保存到配置好的 SecurityContextRepository,同时清除 securityContextHolder 所持有的 SecurityContext;
  • UsernamePasswordAuthenticationFilter
    用于处理来自表单提交的认证。该表单必须提供对应的用户名和密码,其内部还有登录成功或失败后进行处理的 AuthenticationSuccessHandler 和 AuthenticationFailureHandler,这些都可以根据需求做相关改变;
  • FilterSecurityInterceptor
    是用于保护web资源的,使用AccessDecisionManager对当前用户进行授权访问,前面已经详细介绍过了;
  • ExceptionTranslationFilter
    能够捕获来自 FilterChain 所有的异常,并进行处理。但是它只会处理两类异常:AuthenticationException 和 AccessDeniedException,其它的异常它会继续抛出。

7.2、执行流程

Spring Security的执行流程如下:
在这里插入图片描述

  1. 用户提交用户名、密码被SecurityFilterChain中的UsernamePasswordAuthenticationFilter过滤器获取到,封装为请求Authentication,通常情况下是UsernamePasswordAuthenticationToken这个实现类。
  2. 然后过滤器将Authentication提交至认证管理器(AuthenticationManager)进行认证。
  3. 认证成功后,AuthenticationManager身份管理器返回一个被填充满了信息的(包括上面提到的权限信息,身份信息,细节信息,但密码通常会被移除)Authentication实例。
  4. SecurityContextHolder安全上下文容器将第3步填充了信息的Authentication,通过SecurityContextHolder.getContext().setAuthentication(…)方法,设置到其中。
  5. 可以看出AuthenticationManager接口(认证管理器)是认证相关的核心接口,也是发起认证的出发点,它的实现类为ProviderManager。而Spring Security支持多种认证方式,因此ProviderManager维护着一个List列表,存放多种认证方式,最终实际的认证工作是由AuthenticationProvider完成的。咱们知道web表单的对应的AuthenticationProvider实现类为DaoAuthenticationProvider,它的内部又维护着一个UserDetailsService负责UserDetails的获取。最终AuthenticationProvider将UserDetails填充至Authentication。

三、连数据库

1、实体类信息

@Data
@TableName("xc_user")
public class XcUser implements Serializable {

    private static final long serialVersionUID = 1L;

    private String id;

    private String userName;

    private String password;

    private String salt;

    private String name;
    private String nickname;
    private String wxUnionid;
    private String companyId;
    /**
     * 头像
     */
    private String userpic;

    private String utype;

    private Timestamp birthday;

    private String sex;

    private String email;

    private String cellphone;

    private String qq;

    /**
     * 用户状态
     */
    private String status;

    private Timestamp createTime;

    private Timestamp updateTime;

}

2、数据库表

DROP TABLE IF EXISTS `xc_user`;
CREATE TABLE `xc_user`  (
  `id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `user_name` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `password` varchar(96) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `salt` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `wx_unionid` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '微信unionid',
  `nickname` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '昵称',
  `name` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `userpic` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '头像',
  `company_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `utype` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `birthday` datetime NULL DEFAULT NULL,
  `sex` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `email` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `cellphone` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `qq` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `status` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户状态',
  `create_time` datetime NOT NULL,
  `update_time` datetime NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `unique_user_username`(`user_name`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;

3、mapper

@Mapper
public interface XcUserMapper extends BaseMapper<XcUser> {

}

4、实现UserDetailsService接口,重写loadUserByUsername方法

package com.pzz.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.pzz.domain.LoginUser;
import com.pzz.mapper.XcUserMapper;
import com.pzz.po.XcUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
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 org.springframework.util.ObjectUtils;

/**
 * 用户验证处理
 *
 * @author ruoyi
 */
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class);

    @Autowired
    private XcUserMapper xcUserMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        LambdaQueryWrapper<XcUser> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(XcUser::getUserName, username);
        XcUser xcUsers = xcUserMapper.selectOne(lambdaQueryWrapper);
        //如果沒有用戶就拋出异常
        if (ObjectUtils.isEmpty(xcUsers)) {
            throw new RuntimeException("用户名或密码错误");
        }
        //查到用户,返回信息
        return new LoginUser(xcUsers);
    }
}

5、封装返回的UserDetails

@Data
public class LoginUser implements UserDetails
{
    private static final long serialVersionUID = 1L;

    /**
     * 用户ID
     */
    private Long userId;

    /**
     * 部门ID
     */
    private Long deptId;

    /**
     * 用户唯一标识
     */
    private String token;

    /**
     * 登录时间
     */
    private Long loginTime;

    /**
     * 过期时间
     */
    private Long expireTime;

    /**
     * 登录IP地址
     */
    private String ipaddr;

    /**
     * 登录地点
     */
    private String loginLocation;

    /**
     * 浏览器类型
     */
    private String browser;

    /**
     * 操作系统
     */
    private String os;

    /**
     * 权限列表
     */
    private Set<String> permissions;

    /**
     * 用户信息
     */
    private XcUser user;

    public LoginUser()
    {
    }

    public LoginUser(XcUser user)
    {
        this.user = user;
    }

    public LoginUser(XcUser user, Set<String> permissions)
    {
        this.user = user;
        this.permissions = permissions;
    }

    public LoginUser(Long userId, Long deptId, XcUser user, Set<String> permissions)
    {
        this.userId = userId;
        this.deptId = deptId;
        this.user = user;
        this.permissions = permissions;
    }

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

    @Override
    public String getUsername()
    {
        return user.getUserName();
    }

    /**
     * 账户是否未过期,过期无法验证
     */
    @Override
    public boolean isAccountNonExpired()
    {
        return true;
    }

    /**
     * 指定用户是否解锁,锁定的用户无法进行身份验证
     * 
     * @return
     */
    @Override
    public boolean isAccountNonLocked()
    {
        return true;
    }

    /**
     * 指示是否已过期的用户的凭据(密码),过期的凭据防止认证
     * 
     * @return
     */
    @Override
    public boolean isCredentialsNonExpired()
    {
        return true;
    }

    /**
     * 是否可用 ,禁用的用户不能身份验证
     * 
     * @return
     */
    @Override
    public boolean isEnabled()
    {
        return true;
    }

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

6、明文登录

6.1、设置明文

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
	    @Bean
    public PasswordEncoder passwordEncoder() {
        //密码为明文方式
        return NoOpPasswordEncoder.getInstance();
//        return new BCryptPasswordEncoder();
    }
}

6.2、测试

在这里插入图片描述

在这里插入图片描述

7、密码加密登录

7.1、设置密码加密

    @Bean
    public PasswordEncoder passwordEncoder() {
//        //密码为明文方式
//        return NoOpPasswordEncoder.getInstance();
        return new BCryptPasswordEncoder();
    }

7.2、生成密码,存入数据库

在这里插入图片描述在这里插入图片描述

7.3、测试,登录成功

在这里插入图片描述

8、注意,把自定义的用户名密码去掉

在这里插入图片描述

四、整合jwt,登录成功返回用户信息

1、引入依赖

		<!-- redis 缓存操作 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>

		<!-- JWT-->
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt</artifactId>
			<version>0.5.1</version>
		</dependency>

2、JwtUtils工具类

package com.pzz.utils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import java.util.Date;

public class JwtUtils {
  
    private static final String SECRET_KEY = "yourSecretKey";
    private static final long EXPIRATION_TIME = 86400000; // 过期时间为1天

    /**
     * 用于生成JWT。
     * 它接收一个用户ID作为参数,并使用内置的算法和秘钥生成一个包含用户ID和过期时间的签名。
     * @param userId
     * @return
     */
    public static String generateToken(String userId) {
        Date expirationDate = new Date(System.currentTimeMillis() + EXPIRATION_TIME);
        return Jwts.builder()
                .setSubject(userId)
                .setExpiration(expirationDate)
                .signWith(SignatureAlgorithm.HS512, SECRET_KEY)
                .compact();
    }

    /**
     * 用于从JWT中提取用户ID。
     * 它接收一个JWT令牌作为参数,并解析其中的主题(主体)部分来获取用户ID。
     * @param token
     * @return
     */
    public static String getUserIdFromToken(String token) {
        Claims claims = Jwts.parser()
                .setSigningKey(SECRET_KEY)
                .parseClaimsJws(token)
                .getBody();

        return claims.getSubject();
    }

    /**
     * 用于验证JWT的有效性。
     * 它接收一个JWT令牌作为参数,并解析和验证签名。
     * 如果令牌有效且未过期,则返回true;否则返回false。
     * @param token
     * @return
     */
    public static boolean isTokenValid(String token) {
        try {
            Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token);
            return true;
        } catch (Exception e) {
            return false;
        }
    }
}

3、redis 工具类

package com.pzz.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * spring redis 工具类
 *
 * @author ruoyi
 **/
@SuppressWarnings(value = { "unchecked", "rawtypes" })
@Component
public class RedisCache
{
    @Autowired
    public RedisTemplate redisTemplate;

    /**
     * 缓存基本的对象,Integer、String、实体类等
     *
     * @param key 缓存的键值
     * @param value 缓存的值
     */
    public <T> void setCacheObject(final String key, final T value)
    {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 缓存基本的对象,Integer、String、实体类等
     *
     * @param key 缓存的键值
     * @param value 缓存的值
     * @param timeout 时间
     * @param timeUnit 时间颗粒度
     */
    public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
    {
        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
    }

    /**
     * 设置有效时间
     *
     * @param key Redis键
     * @param timeout 超时时间
     * @return true=设置成功;false=设置失败
     */
    public boolean expire(final String key, final long timeout)
    {
        return expire(key, timeout, TimeUnit.SECONDS);
    }

    /**
     * 设置有效时间
     *
     * @param key Redis键
     * @param timeout 超时时间
     * @param unit 时间单位
     * @return true=设置成功;false=设置失败
     */
    public boolean expire(final String key, final long timeout, final TimeUnit unit)
    {
        return redisTemplate.expire(key, timeout, unit);
    }

    /**
     * 获取有效时间
     *
     * @param key Redis键
     * @return 有效时间
     */
    public long getExpire(final String key)
    {
        return redisTemplate.getExpire(key);
    }

    /**
     * 判断 key是否存在
     *
     * @param key 键
     * @return true 存在 false不存在
     */
    public Boolean hasKey(String key)
    {
        return redisTemplate.hasKey(key);
    }

    /**
     * 获得缓存的基本对象。
     *
     * @param key 缓存键值
     * @return 缓存键值对应的数据
     */
    public <T> T getCacheObject(final String key)
    {
        ValueOperations<String, T> operation = redisTemplate.opsForValue();
        return operation.get(key);
    }

    /**
     * 删除单个对象
     *
     * @param key
     */
    public boolean deleteObject(final String key)
    {
        return redisTemplate.delete(key);
    }

    /**
     * 删除集合对象
     *
     * @param collection 多个对象
     * @return
     */
    public boolean deleteObject(final Collection collection)
    {
        return redisTemplate.delete(collection) > 0;
    }

    /**
     * 缓存List数据
     *
     * @param key 缓存的键值
     * @param dataList 待缓存的List数据
     * @return 缓存的对象
     */
    public <T> long setCacheList(final String key, final List<T> dataList)
    {
        Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
        return count == null ? 0 : count;
    }

    /**
     * 获得缓存的list对象
     *
     * @param key 缓存的键值
     * @return 缓存键值对应的数据
     */
    public <T> List<T> getCacheList(final String key)
    {
        return redisTemplate.opsForList().range(key, 0, -1);
    }

    /**
     * 缓存Set
     *
     * @param key 缓存键值
     * @param dataSet 缓存的数据
     * @return 缓存数据的对象
     */
    public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet)
    {
        BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
        Iterator<T> it = dataSet.iterator();
        while (it.hasNext())
        {
            setOperation.add(it.next());
        }
        return setOperation;
    }

    /**
     * 获得缓存的set
     *
     * @param key
     * @return
     */
    public <T> Set<T> getCacheSet(final String key)
    {
        return redisTemplate.opsForSet().members(key);
    }

    /**
     * 缓存Map
     *
     * @param key
     * @param dataMap
     */
    public <T> void setCacheMap(final String key, final Map<String, T> dataMap)
    {
        if (dataMap != null) {
            redisTemplate.opsForHash().putAll(key, dataMap);
        }
    }

    /**
     * 获得缓存的Map
     *
     * @param key
     * @return
     */
    public <T> Map<String, T> getCacheMap(final String key)
    {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * 往Hash中存入数据
     *
     * @param key Redis键
     * @param hKey Hash键
     * @param value 值
     */
    public <T> void setCacheMapValue(final String key, final String hKey, final T value)
    {
        redisTemplate.opsForHash().put(key, hKey, value);
    }

    /**
     * 获取Hash中的数据
     *
     * @param key Redis键
     * @param hKey Hash键
     * @return Hash中的对象
     */
    public <T> T getCacheMapValue(final String key, final String hKey)
    {
        HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
        return opsForHash.get(key, hKey);
    }

    /**
     * 获取多个Hash中的数据
     *
     * @param key Redis键
     * @param hKeys Hash键集合
     * @return Hash对象集合
     */
    public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
    {
        return redisTemplate.opsForHash().multiGet(key, hKeys);
    }

    /**
     * 删除Hash中的某条数据
     *
     * @param key Redis键
     * @param hKey Hash键
     * @return 是否成功
     */
    public boolean deleteCacheMapValue(final String key, final String hKey)
    {
        return redisTemplate.opsForHash().delete(key, hKey) > 0;
    }

    /**
     * 获得缓存的基本对象列表
     *
     * @param pattern 字符串前缀
     * @return 对象列表
     */
    public Collection<String> keys(final String pattern)
    {
        return redisTemplate.keys(pattern);
    }
}

4、redis的配置

spring:
  redis:
    host: 127.0.0.1
    post: 6379

5、创建authenticationManagerBean对象,和配置安全拦截机制

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

	//配置安全拦截机制
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http     //关闭csrf
                .csrf().disable()
                //不通过session获取securityContext
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                //放行登录接口
                .antMatchers("/login").anonymous()
                //除了上面的请求,其他的请求必须授权认证
                .anyRequest().authenticated();
    }
}

6、登录的接口

    /**
     * 登录的方法
     * @return
     */
    @PostMapping("/login")
    public R loginSuccess(@RequestBody XcUser xcUser) {
        return loginService.login(xcUser);
    }

7、登录的实现,使用用户ID生成jwt

@Service
public class LoginServiceImpl implements LoginService {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private RedisCache redisCache;

    /**
     * 登录认证的方法
     * @param xcUser
     * @return
     */
    @Override
    public R login(XcUser xcUser) {
        //Authentication authenticate 进行用户认证
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(xcUser.getUserName(), xcUser.getPassword());
        Authentication authenticate = authenticationManager.authenticate(authenticationToken);

        //认证失败,返回异常
        if (ObjectUtils.isEmpty(authenticate)){
            throw  new RuntimeException("登录失败...");
        }
        //认证通过,生成jwt,返回
        LoginUser loginUser = (LoginUser)authenticate.getPrincipal();
        String userId = loginUser.getUser().getId().toString();
        //根据用户ID生成jwt
        String jwt = JwtUtils.generateToken(userId);
        Map<String,String> map = new HashMap<>();
        map.put("token",jwt);
        //把完整的用户信息存入redis,userId作为key
        redisCache.setCacheObject("login:"+userId,loginUser);
        String username = loginUser.getUser().getUserName().toString();
        return R.ok(map,"登录成功,欢迎:"+username);
    }
}

8、测试接口

在这里插入图片描述

五、认证过滤器

当用户登录后,生成的token信息存入headers中,每次请求应该携带token,作为是否登录的验证

1、认证过滤器JwtAuthenticationTokenFilter

package com.pzz.filter;

import com.pzz.domain.LoginUser;
import com.pzz.utils.JwtUtils;
import com.pzz.utils.RedisCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.rmi.RemoteException;
import java.util.Objects;

/**
 * @author pzz
 * @date 2023/7/26 22:51
 */
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Autowired
    private RedisCache redisCache;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //获取token
        String token = request.getHeader("token");
        if (!StringUtils.hasText(token)){
            //放行
            filterChain.doFilter(request,response);
            return;
        }
        //解析token
        String userId;
        try {
            userId = JwtUtils.getUserIdFromToken(token);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("token非法");
        }

        //从redis中获取用户信息
        String redisKey = "login:"+userId;
        LoginUser loginUser = (LoginUser)redisCache.getCacheObject(redisKey);
        if (Objects.isNull(loginUser)){
            throw new RuntimeException("用户未登录");
        }

        //存入securContextHolder
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser,
                null, null);
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        //放行
        filterChain.doFilter(request,response);
    }
}

2、配置认证过滤器

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
	  @Autowired
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

	  @Override
    protected void configure(HttpSecurity http) throws Exception {
		//配置认证过滤器
    	http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
	}
}

3、根据ID查询用户信息的方法

    /**
     * 根据用户ID查询用户信息
     * @param id
     * @return
     */
    @RequestMapping("/user/{id}")
    public R getuser(@PathVariable("id") String id) {
        XcUser xcUser = userMapper.selectById(id);
        return R.ok(xcUser);
    }

4、测试

4.1、未携带token

在这里插入图片描述

4.2、携带token

在这里插入图片描述

5、退出登录

5.1、controller代码

    /**
     * 注销登录
     */
    @GetMapping("/logout")
    public R logout(){
        return loginService.logout();
    }

5.2、service代码

    /**
     * 退出登录
     * @return
     */
    @Override
    public R logout() {
        //获取SecurityContextHolder中的用户ID
        UsernamePasswordAuthenticationToken authenticationToken = (UsernamePasswordAuthenticationToken)SecurityContextHolder.getContext().getAuthentication();
        LoginUser loginUser = (LoginUser)authenticationToken.getPrincipal();
        String userId = loginUser.getUser().getId();
        //删除redis中的值
        redisCache.deleteObject("login:"+userId);
        return R.ok(null,"注销成功");
    }

六、授权

1、相关配置

1.1、开启访问资源所需权限

@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {}

1.2、访问路径设置权限

    @RequestMapping("/hello")
    @PreAuthorize("hasAuthority('test')")//拥有test权限方可访问
    public String hello() {
        return "hello";
    }

2、封装权限、给用户添加权限

2.1、封装用户的实体类,用户设置权限

@Data
public class LoginUser implements UserDetails{
	    /**
     * 权限列表
     */
    private Set<String> permissions;

	public LoginUser(XcUser user, Set<String> permissions)
    {
        this.user = user;
        this.permissions = permissions;
    }

	@JSONField(serialize = false)
    private List<SimpleGrantedAuthority> authorities;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities()
    {
        if (authorities != null){
            return authorities;
        }
        authorities = permissions.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
        return authorities;
    }
}

2.2、用户登录时查询用户的权限,当前设置用户的权限为test

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
	    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //查询用户的权限
        Set<String> set = new HashSet<>();
        set.add("test");
        //查到用户,返回信息
        return new LoginUser(xcUsers,set);
    }
}

2.3、过滤器拦截权限

在这里插入图片描述

3、测试

3.1、登录后,访问hello

在这里插入图片描述

3.2、修改hello的访问权限为test111,测试

在这里插入图片描述
在这里插入图片描述

4、从数据查用户的权限

权限一般使用五个表

  1. 用户表
  2. 角色表
  3. 用户_角色_中间表
  4. 菜单表(权限表)
  5. 角色_权限_中间表

4.1、查询权限的SQL

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.pzz.mapper.MenuMapper">

    <select id="selectPermsByUserId" resultType="java.lang.String" parameterType="integer">
        SELECT
            DISTINCT
            sm.perms
        from
            sys_user_role sur
        left join sys_role sr on sur.role_id = sr.role_id
        LEFT JOIN sys_role_menu srm on sr.role_id = srm.role_id
        left join sys_menu sm on srm.menu_id = sm.menu_id
        where
            sm.perms is not null
            and sm.perms != ""
            and sur.user_id = #{userId}
    </select>
</mapper>

4.2、登录成功时,设置用户的权限

在这里插入图片描述

4.3、修改访问hello接口的权限

在这里插入图片描述

4.4、访问测试

在这里插入图片描述

七、跨域

1、springboot设置跨域(省略)

2、security设置跨域

 //设置跨域
 http.cors();

八、security认证流程

在这里插入图片描述

结束!
hy:23


							真正的幸福来自于内心的满足和积极的生活态度。

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

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

相关文章

RuoYi-Vue代码常见漏洞修复

Redis未设置密码 解决方法&#xff1a; 此方法永久生效* 找到requirepass关键字&#xff0c;后面就是跟的密码&#xff0c;默认情况下是注释掉的&#xff0c;即默认不需要密码&#xff0c;如下&#xff1a;  打开注释&#xff0c;设置为自己的密码&#xff0c;重启即可 数据…

Scrum敏捷开发管理流程+scrum工具免费

Leangoo领歌它覆盖了敏捷项目研发全流程&#xff0c;包括小型团队Scrum敏捷开发&#xff0c;规模化敏捷SAFe&#xff0c;Scrum of Scrums大规模敏捷。它提供了灵活的敏捷模板和极致的协作体验&#xff0c;可以让团队快速上手&#xff0c;快速落地Scrum敏捷开发管理。 首先建立产…

06 HTTP(下)

06 HTTP&#xff08;下&#xff09; 介绍服务器如何响应请求报文&#xff0c;并将该报文发送给浏览器端。介绍一些基础API&#xff0c;然后结合流程图和代码对服务器响应请求报文进行详解。 基础API部分&#xff0c;介绍stat、mmap、iovec、writev。 流程图部分&#xff0c;描…

ssm员工管理系统

ssm员工管理系统 java员工管理系统 员工管理系统 运行环境&#xff1a; JAVA版本&#xff1a;JDK1.8 IDE类型&#xff1a;IDEA、Eclipse都可运行 数据库类型&#xff1a;MySql&#xff08;8.x版本都可&#xff09; 硬件环境&#xff1a;Windows 功能介绍&#xff1a; 1.用户…

html学习3(表格table、列表list)

1、html表格由<table>标签来定义。 <thead>用来定义表格的标题部分&#xff0c;其内部用 <th > 元素定义列的标题&#xff0c;可以使其在表格中以粗体显示&#xff0c;与普通单元格区分开来。<tbody>用来定义表格的主体部分&#xff0c;其内部用<t…

Java版知识付费免费搭建+spring cloud 前后端分离实现知识付费平台

提供职业教育、企业培训、知识付费系统搭建服务。系统功能包含&#xff1a;录播课、直播课、题库、营销、公司组织架构、员工入职培训等。 提供私有化部署&#xff0c;免费售后&#xff0c;专业技术指导&#xff0c;支持PC、APP、H5、小程序多终端同步&#xff0c;支持二次开发…

CompletableFuture 详解

目录 简单介绍 常见操作 创建 CompletableFuture new 关键字 静态工厂方法 处理异步结算的结果 简单介绍 CompletableFuture 同时实现了 Future 和 CompletionStage 接口。 public class CompletableFuture<T> implements Future<T>, CompletionStage<T…

小白到运维工程师自学之路 第六十集 (docker的概述与安装)

一、概述 1、客户&#xff08;老板&#xff09;-产品-开发-测试-运维项目周期不断延后&#xff0c;项目质量差。 随着云计算和DevOps生态圈的蓬勃发展&#xff0c;产生了大量优秀的系统和软件。软件开发人员可以自由选择各种软件应用环境。但同时带来的问题就是需要维护一个非…

HTTP——HTTP报文内的HTTP信息

HTTP 通信过程包括从客户端发往服务器端的请求及从服务器端返回客户端的响应。本章就让我们来了解一下请求和响应是怎样运作的。 HTTP 一、HTTP报文二、请求报文及响应报文的结构三、编码提升传输速率1、报文主体和实体主题的差异2、压缩传输的内容编码3、分割发送的分块传输编…

【数据分享】1999—2021年地级市地区生产总值及一二三产构成数据(Shp/Excel格式)

在之前的文章中&#xff0c;我们分享过基于2000-2022年《中国城市统计年鉴》整理的1999-2021年地级市的人口相关数据、各类用地面积数据、污染物排放和环境治理相关数据、房地产投资情况和商品房销售面积、社会消费品零售总额和年末金融机构存贷款余额、地方一般公共预算收支状…

ABeam China Global | 探索赞比亚:非洲市场商机、产业发展与劳动挑战(下)

前情回顾 ABeam China Global | 探索赞比亚&#xff1a;非洲市场商机、产业发展与劳动挑战&#xff08;上&#xff09; 在「探索赞比亚」系列的上期&#xff0c;我们介绍了森大集团、非洲市场概述&#xff0c;以及“SADC”这一地区经济组织。本期我们将视角聚焦到赞比亚&…

Android Studio多渠道打包

使用环境&#xff1a; Android studio 多渠道打包 使用方法&#xff1a; 1 APP下build.gradle文件 flavorDimensions "default"productFlavors {huawei {dimension "default"manifestPlaceholders [ channel:"huawei" ]}xiaomi {dimension &…

LLM微调 | Adapter: Parameter-Efficient Transfer Learning for NLP

目的&#xff1a;大模型预训练微调范式&#xff0c;微调成本高。adapter只只微调新增的小部分参数【但adapter增加了模型层数&#xff0c;引入了额外的推理延迟。】 Adapters最初来源于CV领域的《Learning multiple visual domains with residual adapters》一文&#xff0c;其…

数据库信息速递, RAFT 原生系统是未来数据流式系统的未来

开头还是介绍一下群&#xff0c;如果感兴趣polardb ,mongodb ,mysql ,postgresql ,redis 等有问题&#xff0c;有需求都可以加群群内有各大数据库行业大咖&#xff0c;CTO&#xff0c;可以解决你的问题。加群请联系 liuaustin3 &#xff0c;在新加的朋友会分到2群&#xff08;共…

功能测试之兼容性测试点和注意项

一&#xff1a;兼容性测试的概念&#xff1a;就是验证开发出来的程序在特定的运行环境中与特定的软件、硬件或数据相组合是否能正常运行、有无异常的测试过程。 二&#xff1a;兼容性测试的分类&#xff1a; &#xff08;1&#xff09;浏览器兼容性测试 指的是在浏览器上检查…

第四课:逻辑控制

1.分支语句 &#xff08;1&#xff09;if语句 练习 1.判断一个数字是奇数还是偶数 public static void main(String[] args) {int a 10;if (a % 2 1){System.out.println("a是奇数");}else{System.out.println("a是偶数");}} 2.判断一个年份是否为闰年…

Django学习记录:初步认识django以及实现了简单的网页登录页面的前后端开发

Django学习记录&#xff1a;初步认识django以及实现了简单的网页登录页面的前后端开发 1、可以先删去template文件夹&#xff0c;并在setting里面删掉这一行 2、在pycharm中创建app&#xff1a; 3、启动app&#xff1a;编写URL与视图函数关系【urls.py】 ​ 编写视图函数【vi…

HCIA-datacom数通题库和录播视频资料

HCIA-Datacom&#xff0c;是华为数通认证的初级考试&#xff0c;培训与认证具备数通基础通用知识和技能水平的工程师&#xff0c;只是入门了解数通的一些基础通用知识&#xff0c;适用于小白了解和学习数通知识点起点。 个人建议还是有必要考的&#xff0c;如果在企业考试考试…

Java版本spring cloud 工程项目管理系统源码

​ ​工程项目管理系统是指从事工程项目管理的企业&#xff08;以下简称工程项目管理企业&#xff09;受业主委托&#xff0c;按照合同约定&#xff0c;代表业主对工程项目的组织实施进行全过程或若干阶段的管理和服务。 如今建筑行业竞争激烈&#xff0c;内卷严重&#xff0c…

助力保险行业数字化创新,麒麟信安参展2023中国财险科技应用高峰论坛

2023年7月27日&#xff0c;由中科软科技股份有限公司主办的“中国财险科技应用高峰论坛”在北京古北水镇成功举办。作为享誉中国保险科技界的盛会&#xff0c;本次活动以“数智保险 创新未来”主题&#xff0c;汇聚全国数百位保险公司主管领导、资深保险行业信息化专家&#xf…