Spring Security认证与授权

1  Spring Security介绍

        Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。由于它是Spring生态系统中的一员,因此它伴随着整个Spring生态系统不断修正、升级,在spring boot项目中加入springsecurity更是十分简单,使用Spring Security 减少了为企业系统安全控制编写大量重复代码的工作。(原名叫acegi在2007年底才更名为 Spring Security

2 认证流程

Spring Security 功能的实现主要是靠一系列的过滤器链相互配合来完成的。以下是项目启动时打印的默认安全过滤器链(集成5.2.0):

[
    org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@5054e546,
    org.springframework.security.web.context.SecurityContextPersistenceFilter@7b0c69a6,
    org.springframework.security.web.header.HeaderWriterFilter@4fefa770,
    org.springframework.security.web.csrf.CsrfFilter@6346aba8,
    org.springframework.security.web.authentication.logout.LogoutFilter@677ac054,
    org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@51430781,
    org.springframework.security.web.savedrequest.RequestCacheAwareFilter@4203d678,
    org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@625e20e6,
    org.springframework.security.web.authentication.AnonymousAuthenticationFilter@19628fc2,
    org.springframework.security.web.session.SessionManagementFilter@471f8a70,
    org.springframework.security.web.access.ExceptionTranslationFilter@3e1eb569,
    org.springframework.security.web.access.intercept.FilterSecurityInterceptor@3089ab62
]

  • WebAsyncManagerIntegrationFilter
  • SecurityContextPersistenceFilter
  • HeaderWriterFilter
  • CsrfFilter
  • LogoutFilter
  • UsernamePasswordAuthenticationFilter
  • RequestCacheAwareFilter
  • SecurityContextHolderAwareRequestFilter
  • AnonymousAuthenticationFilter
  • SessionManagementFilter
  • ExceptionTranslationFilter
  • FilterSecurityInterceptor

详细解读可以参考:Spring Security 核心过滤器链分析_defaultsecurityfilterchain-CSDN博客

在解析前,先说说两个至关重要的类:OncePerRequestFilter和GenericFilterBean,在过滤器链的过滤器中,或多或少间接或直接继承到

OncePerRequestFilter顾名思义,能够确保在一次请求只通过一次filter,而不需要重复执行。
GenericFilterBean是javax.servlet.Filter接口的一个基本的实现类
GenericFilterBean将web.xml中filter标签中的配置参数-init-param项作为bean的属性
GenericFilterBean可以简单地成为任何类型的filter的父类
GenericFilterBean的子类可以自定义一些自己需要的属性
GenericFilterBean,将实际的过滤工作留给他的子类来完成,这就导致了他的子类不得不实现doFilter方法
GenericFilterBean不依赖于Spring的ApplicationContext,Filters通常不会直接读取他们的容器信息(ApplicationContext concept)而是通过访问spring容器(Spring root application context)中的service beans来获取,通常是通过调用filter里面的getServletContext() 方法来获取

3 SpringSecurity的使用(用户认证加注册)

步骤一:pom.xml中导入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- jwt依赖-->
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.10.3</version>
</dependency>
<!--redis依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--mybatisplus依赖-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.1</version>
</dependency>
<!--mysql依赖-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.29</version>
</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>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-test</artifactId>
    <scope>test</scope>
</dependency>

当导入这个依赖后,在访问任何接口的时候都会被过滤器拦截,它会先跳到Spring Security默认的登录页面去。如下图所示

需要登录后才来访问请求的接口(用户名:user,密码会自动生成(在idea控制台上面可以找到) 

当然这个页面主要用于测试用的,我们实际开发是不会用这个页面的。

步骤二 :定义PasswordEncoder密码解析器解释

Spring Security官方推荐的密码解析器。可以通过strength控制加密强度,默认10。

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

BCryptPasswordEncoder()提供了有参构造器,可以传入一个4到31的整数,设置值越大密码编码越安全,但是性能越越低,因此这个值正常不要设置很大,若不设置的话默认为10。

当然有可以使用自定义的密码解析器解释,,但是一般很少自己去写,除非特殊要求。定义格式如下:

public class MyPasswordEncoder implements PasswordEncoder {
    @Override
    public String encode(CharSequence rawPassword) {
        System.out.println("自定义密码解析器 - encode方法执行");
        return rawPassword.toString();
    }

    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        System.out.println("自定义密码解析器 - matches方法执行");
        // 先使用encode方法,用相同的加密策略,加密明文,再对比密文。
        return encode(rawPassword).equals(encodedPassword);


    }

    @Override
    public boolean upgradeEncoding(String encodedPassword) {
        return PasswordEncoder.super.upgradeEncoding(encodedPassword);
    }
}
@Configuration
public class MySecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Bean
    public PasswordEncoder passwordEncoder(){
//自定义密码解析器
        return new MyPasswordEncoder();
    }
}

步骤三:实现UserDetailsService接口

@Component
public class MyUserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private UserMapper userMapper;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        System.out.println("自定义登录服务 - loadUserByUsername方法执行");
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
        lqw.eq(User::getUsername, username);
        // 根据用户名查询用户
        User user = userMapper.selectOne(lqw);
        // 判断用户名是否存在
        if(user == null){
            System.out.println("用户名:" + username + " 不存在");
            // 用户名不存在
            throw new UsernameNotFoundException("用户名或密码错误");

        }
        LoginUser loginUser = new LoginUser();
        loginUser.setUser(user);

        return loginUser;
    }
 
}

 loadUserByUsername需要返回UserDetails对象,而UserDetails其实是一个接口,因此,我们可以去实现这个接口。 loadUserByUsername中要是出现异常,会自动执行security的**/err接口

@Data
@NoArgsConstructor
@AllArgsConstructor
// 解决后续redis读取数据时反序列化报错
@JsonIgnoreProperties(ignoreUnknown = true)
public class LoginUser implements UserDetails {
    //这是一个实体类,需要自己提前定义
    private User user;

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

    /**
     * 框架中会自动调用获取用户名和密码的操作,所以返回值要重写一下
     * @return
     */
    @Override
    public String getPassword() {
        return user.getPassword();
    }

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

    /**
     *     布尔值记得改为True,否则可能无法访问
      */
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

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

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

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

步骤四:取消security的默认页面

下列代码添加到步骤二的类中。

 @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                //关闭csrf
                .csrf().disable()
                //不通过Session获取SecurityContext
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                // 对于登录接口 允许匿名访问
                .antMatchers("/user/login").anonymous()
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated();
        http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
        
    }


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

步骤五:自定义登录请求

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private IUserService userService;
  //登录
    @PostMapping("/login")
    public AjaxResult login(User user){
        String result=userService.login(user);
        return AjaxResult.success(result);
    }
}

public interface IUserService extends IService<User> {
//登录
    String login(User user);
}
package com.mashang.service.impl;

import com.mashang.config.RedisUtil;
import com.mashang.entity.LoginUser;
import com.mashang.entity.User;
import com.mashang.mapper.UserMapper;
import com.mashang.service.IUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.mashang.utils.JWTUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;

import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author author
 * @since 2024-09-09
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private RedisUtil redisUtil;
    @Override
/**
 * 用户登录方法
 * 该方法主要用于用户登录,通过验证用户的用户名和密码来生成并返回一个JWT(Json Web Token)
 *
 * @param user 用户对象,包含用户名和密码
 * @return 返回一个JWT,用于后续的用户身份验证
 * @throws RuntimeException 如果用户认证失败,则抛出运行时异常
 */
public String login(User user) {
    // 创建一个包含用户名和密码的认证令牌
    UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword());

    // 使用认证管理器进行用户认证((最终会调用到 loadUserByUsername方法,返回认证的信息))
    Authentication authenticate = authenticationManager.authenticate(token);

    // 检查认证结果,如果为空,则记录错误并抛出异常
    if (Objects.isNull(authenticate)) {
        log.error("认证失败");
        throw new RuntimeException("认证失败");
    }

    // 从认证对象中获取登录用户信息
    LoginUser loginUser = (LoginUser) authenticate.getPrincipal();

    // 为用户生成JWT
    String jwt = JWTUtil.createToken(loginUser.getUser());

    // 将用户信息存储到Redis中,设置过期时间
    redisUtil.setCacheObject("user:" + loginUser.getUser().getId(), loginUser, 30, TimeUnit.MINUTES);

    // 返回生成的JWT
    return jwt;
}

}

步骤六:导入工具类

1Redis序列化

package com.mashang.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {
    // 定义了一个RedisTemplate
    @Bean
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        // RedisTemplate 为了自己方便一般直接使用<String,Object>
        RedisTemplate<String, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        // 序列化配置
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
        ObjectMapper om = new ObjectMapper();
        // 设置可见度
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 启动默认的类型
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        // 序列化类,对象映射设置
        jackson2JsonRedisSerializer.setObjectMapper(om);
        // String的序列化
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key采用String的序列化
        template.setHashKeySerializer(stringRedisSerializer);
        // value采用jackson的序列化
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value采用jackson的序列化
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        return template;
    }
}

2 Redis工具类

package com.mashang.config;

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 RedisUtil {
    @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 缓存键值
     * @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 long deleteObject(final Collection collection) {
        return redisTemplate.delete(collection);
    }

    /**
     * 缓存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
     * @param hKey
     */
    public void delCacheMapValue(final String key, final String hKey) {
        HashOperations hashOperations = redisTemplate.opsForHash();
        hashOperations.delete(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);
    }

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

3 JWT工具类

package com.mashang.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.mashang.entity.User;
import org.springframework.stereotype.Component;

import java.util.Calendar;
@Component
public class JWTUtil {
    // 加密的秘钥封装一下
    private static final String SECRET = "jhkasdfghadsfgsdafkl";
    // id字段
    private static final String ID_FIELD = "userID";
    // token的有效时间 30 天
    private static final Integer TIME_OUT_DAY = 30;

    /**
     * 创建token
     *
     * @param user 登陆的用户
     * @return 返回Token字符串
     */
    public static String createToken(User user) {
        // 获取日历对象实例
        Calendar calendar = Calendar.getInstance();
        // 在当前日期加上 TIME_OUT_DAY 的时间,用于设置过期时间
        calendar.add(Calendar.DATE, TIME_OUT_DAY);
        System.out.println(user.getId());
        // 创建jwt
        return JWT.create()
            // 可以在token中设置数据,设置一个userId为用户的id
            // 后续可以直接在token中获取id
            .withClaim(ID_FIELD, user.getId())
            // 设置桂平群殴瑟吉欧靠门
            .withExpiresAt(calendar.getTime())
            // Algorithm.HMAC256(SECRET) 使用HMAC256的加密方式
            // secret 指的是秘钥,在这个秘钥的基础上,进行加密,加大破解的难度这个秘钥爱写什么写什么
            .sign(Algorithm.HMAC256(SECRET));
    }

    /**
     * 验证JWT,返回为false的时候表示验证失败
     *
     * @param token token字符串
     * @return 返回boolean 表示是否登录成功
     */
    public static boolean verifyToken(String token) {
        try {
            // 验证JWT,验证不通过会报错
            JWT.require(Algorithm.HMAC256(SECRET)).build().verify(token);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 获取用户id,返回值是0表示没有找到id
     *
     * @param token token 字符串
     * @return 返回对应的用户id,如果为0则表示没有用户
     */
    public static Long getUserId(String token) {
        try {
            // 获取id,没有id则会报错
            return JWT.require(Algorithm.HMAC256(SECRET)).build().verify(token).getClaim(ID_FIELD).asLong();
        } catch (Exception e) {
            // 如果报错就返回null表示没有找到对应的用户
            return 0L;
        }
    }
}

 4 数据返回

package com.mashang.yanzhengma;

import java.util.HashMap;
import java.util.Objects;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;

/**
 * 操作消息提醒
 * 
 * @author ruoyi
 */
@ApiModel("操作消息提醒")
public class AjaxResult extends HashMap<String, Object>
{
    private static final long serialVersionUID = 1L;

    /** 状态码 */
    @ApiModelProperty("状态码")
    public static final String CODE_TAG = "code";

    /** 返回内容 */
    @ApiModelProperty("返回内容")
    public static final String MSG_TAG = "msg";

    /** 数据对象 */
    @ApiModelProperty("数据对象")
    public static final String DATA_TAG = "data";

    /**
     * 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。
     */
    public AjaxResult()
    {
    }

    /**
     * 初始化一个新创建的 AjaxResult 对象
     * 
     * @param code 状态码
     * @param msg 返回内容
     */
    public AjaxResult(int code, String msg)
    {
        super.put(CODE_TAG, code);
        super.put(MSG_TAG, msg);
    }

    /**
     * 初始化一个新创建的 AjaxResult 对象
     * 
     * @param code 状态码
     * @param msg 返回内容
     * @param data 数据对象
     */
    public AjaxResult(int code, String msg, Object data)
    {
        super.put(CODE_TAG, code);
        super.put(MSG_TAG, msg);
        if (StringUtils.isNotNull(data))
        {
            super.put(DATA_TAG, data);
        }
    }


    /**
     * 返回成功消息
     * 
     * @return 成功消息
     */
    public static AjaxResult success()
    {
        return AjaxResult.success("操作成功");
    }

    /**
     * 返回成功数据
     * 
     * @return 成功消息
     */
    public static AjaxResult success(Object data)
    {
        return AjaxResult.success("操作成功", data);
    }

    /**
     * 返回成功消息
     * 
     * @param msg 返回内容
     * @return 成功消息
     */
    public static AjaxResult success(String msg)
    {
        return AjaxResult.success(msg, null);
    }

    /**
     * 返回成功消息
     * 
     * @param msg 返回内容
     * @param data 数据对象
     * @return 成功消息
     */
    public static AjaxResult success(String msg, Object data)
    {
        return new AjaxResult(HttpStatus.SUCCESS, msg, data);
    }

    /**
     * 返回警告消息
     *
     * @param msg 返回内容
     * @return 警告消息
     */
    public static AjaxResult warn(String msg)
    {
        return AjaxResult.warn(msg, null);
    }

    /**
     * 返回警告消息
     *
     * @param msg 返回内容
     * @param data 数据对象
     * @return 警告消息
     */
    public static AjaxResult warn(String msg, Object data)
    {
        return new AjaxResult(HttpStatus.WARN, msg, data);
    }

    /**
     * 返回错误消息
     * 
     * @return 错误消息
     */
    public static AjaxResult error()
    {
        return AjaxResult.error("操作失败");
    }

    /**
     * 返回错误消息
     * 
     * @param msg 返回内容
     * @return 错误消息
     */
    public static AjaxResult error(String msg)
    {
        return AjaxResult.error(msg, null);
    }

    /**
     * 返回错误消息
     * 
     * @param msg 返回内容
     * @param data 数据对象
     * @return 错误消息
     */
    public static AjaxResult error(String msg, Object data)
    {
        return new AjaxResult(HttpStatus.ERROR, msg, data);
    }
    public static AjaxResult error(int code,String msg, Object data)
    {
        return new AjaxResult(code, msg, data);
    }

    /**
     * 返回错误消息
     * 
     * @param code 状态码
     * @param msg 返回内容
     * @return 错误消息
     */
    public static AjaxResult error(int code, String msg)
    {
        return new AjaxResult(code, msg, null);
    }

    /**
     * 是否为成功消息
     *
     * @return 结果
     */
    public boolean isSuccess()
    {
        return Objects.equals(HttpStatus.SUCCESS, this.get(CODE_TAG));
    }

    /**
     * 是否为警告消息
     *
     * @return 结果
     */
    public boolean isWarn()
    {
        return Objects.equals(HttpStatus.WARN, this.get(CODE_TAG));
    }

    /**
     * 是否为错误消息
     *
     * @return 结果
     */
    public boolean isError()
    {
        return Objects.equals(HttpStatus.ERROR, this.get(CODE_TAG));
    }

    /**
     * 方便链式调用
     *
     * @param key 键
     * @param value 值
     * @return 数据对象
     */
    @Override
    public AjaxResult put(String key, Object value)
    {
        super.put(key, value);
        return this;
    }
}

5 JWTFilter过滤器

@Component
public class JWTFilter extends OncePerRequestFilter  {
    @Autowired
    protected RedisUtil redisUtil;
    @Autowired
    protected RedisTemplate redisTemplate;
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String token = request.getHeader("token");
        if (token == null ||!JWTUtil.verifyToken(token)) {
//后面还有很多过滤器,会有过滤器进行拦截的           
 filterChain.doFilter(request, response);
            return;
        }


        LoginUser loginUser = (LoginUser)redisUtil.getCacheObject("user:" + JWTUtil.getUserId(token));
        if (loginUser == null){
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(JSONUtil.toJsonStr(AjaxResult.error(401,"请登录","")));
            return;
        }else {
            //更新redis中保存的token的过期时间
            redisTemplate.expire("user:" + JWTUtil.getUserId(token), 30, TimeUnit.MINUTES);
        }
//让后面过滤器不再拦截
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, null);
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        filterChain.doFilter(request, response);


    }


}

步骤七: 注册用户

 @PostMapping("/register")
    public String register(User user){
        BCryptPasswordEncoder encoder=new BCryptPasswordEncoder();
        user.setPassword(encoder.encode(user.getPassword()));
        userService.save(user);
        return "注册成功";
    }

4 授权 

授权需要在数据库,创建五张表:分别是用户表、角色表、权限表,用户角色关联表、角色权限关联表。(建表语句在后面.)

步骤一:启动类开启配置

@EnableGlobalMethodSecurity(prePostEnabled = true)

步骤二,自定义鉴权方法

@Component("ss")
public class MyExpressionUtil {

    public boolean hasPermission(String permission) {
         //未来可以改为,数据库查询,当返回true代表,可以访问,false不能访问接口
        return true;
    }
}

步骤三:在接口上添加注解

@GetMapping("/t1")
    @PreAuthorize("@ss.hasPermission('t1')")
    public String getUserInfo(){
        return "hello";
    }

@PreAuthorize("@ss.hasPermission('t1')")解释:接口上加上这个注解,首先会去容器找名为ss的类,然后找到ss类的hasPermission方法,会将"t1"值作为参数,传给hasPermission方法,意思permission现在就保存着t1,执行完这个方法会返回布尔值,当返回True就表示,可以访问该接口。hasPermission方法内的逻辑,最终可以改为数据库查询。

数据库

CREATE TABLE `tb_permission` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(32) DEFAULT NULL COMMENT '权限名称',
  `url` varchar(255) DEFAULT NULL COMMENT '请求地址',
  `parent_id` int DEFAULT NULL COMMENT '父权限主键',
  `type` varchar(24) DEFAULT NULL COMMENT '权限类型, M - 菜单, A - 子菜单, U - 普通请求',
  `permit` varchar(128) DEFAULT NULL COMMENT '权限字符串描述,如:user:list 用户查看权限 user 用户权限 user:insert 用户新增权限 等',
  `remark` text COMMENT '描述',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;



CREATE TABLE `tb_role` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(32) DEFAULT NULL COMMENT '角色名称',
  `remark` text COMMENT '角色描述',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;



CREATE TABLE `tb_role_permission` (
  `role_id` int DEFAULT NULL COMMENT '角色外键',
  `permission_id` int DEFAULT NULL COMMENT '权限外键'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;


CREATE TABLE `tb_user` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(32) DEFAULT NULL COMMENT '姓名',
  `username` varchar(32) DEFAULT NULL COMMENT '用户名',
  `password` varchar(128) DEFAULT NULL COMMENT '密码',
  `remark` text COMMENT '描述',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;


CREATE TABLE `tb_user_role` (
  `user_id` int DEFAULT NULL COMMENT '用户外键',
  `role_id` int DEFAULT NULL COMMENT '角色外键'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

  • 27
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值