当前位置: 首页 > news >正文

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 {@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}
}

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

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

public class MyPasswordEncoder implements PasswordEncoder {@Overridepublic String encode(CharSequence rawPassword) {System.out.println("自定义密码解析器 - encode方法执行");return rawPassword.toString();}@Overridepublic boolean matches(CharSequence rawPassword, String encodedPassword) {System.out.println("自定义密码解析器 - matches方法执行");// 先使用encode方法,用相同的加密策略,加密明文,再对比密文。return encode(rawPassword).equals(encodedPassword);}@Overridepublic boolean upgradeEncoding(String encodedPassword) {return PasswordEncoder.super.upgradeEncoding(encodedPassword);}
}
@Configuration
public class MySecurityConfiguration extends WebSecurityConfigurerAdapter {@Beanpublic PasswordEncoder passwordEncoder(){
//自定义密码解析器return new MyPasswordEncoder();}
}

步骤三:实现UserDetailsService接口

@Component
public class MyUserDetailsServiceImpl implements UserDetailsService {@Autowiredprivate UserMapper userMapper;@Overridepublic 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;@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return null;}/*** 框架中会自动调用获取用户名和密码的操作,所以返回值要重写一下* @return*/@Overridepublic String getPassword() {return user.getPassword();}@Overridepublic String getUsername() {return user.getUsername();}/***     布尔值记得改为True,否则可能无法访问*/@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}

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

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

 @Overrideprotected 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@Beanpublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}

步骤五:自定义登录请求

@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate 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 {@Autowiredprivate AuthenticationManager authenticationManager;@Autowiredprivate 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();// 为用户生成JWTString jwt = JWTUtil.createToken(loginUser.getUser());// 将用户信息存储到Redis中,设置过期时间redisUtil.setCacheObject("user:" + loginUser.getUser().getId(), loginUser, 30, TimeUnit.MINUTES);// 返回生成的JWTreturn 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 {@Autowiredpublic 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());// 创建jwtreturn 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 数据对象*/@Overridepublic AjaxResult put(String key, Object value){super.put(key, value);return this;}
}

5 JWTFilter过滤器

@Component
public class JWTFilter extends OncePerRequestFilter  {@Autowiredprotected RedisUtil redisUtil;@Autowiredprotected RedisTemplate redisTemplate;@Overrideprotected 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;

相关文章:

Spring Security认证与授权

1 Spring Security介绍 Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。由于它是Spring生态系统中的一员&#xff0c;因此它伴随着整个Spring生态系统不断修正、升级&#xff0c;在spring boot项目中加入springsecurity更是…...

速通GPT:Improving Language Understanding by Generative Pre-Training全文解读

文章目录 速通GPT系列几个重要概念1、微调的具体做法2、任务感知输入变换3、判别式训练模型 Abstract概括分析和观点1. 自然语言理解中的数据问题2. 生成预训练和监督微调的结合3. 任务感知输入变换4. 模型的强大性能 Introduction概括分析和观点1. 自然语言理解的挑战在于对标…...

软件质量保证例题

答案&#xff1a;D 软件质量保证 功能性 适合性 准确性 互操作性 安全保密性 依从性 可靠性 成熟性 容错性 易恢复性 易用性 易理解性 易学性 易操作性 效率 时间特性 资源利用性 维护性 易分析性 易改变性 稳定性 易测试性 可移植性 适应性 易安装性 一致性 易替换…...

动态规划算法---04.斐波那契数列模型_解码方法_C++

题目链接&#xff1a;91. 解码方法 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/decode-ways/description/ 一、题目解析 题目&#xff1a; 题目大意&#xff1a;从题目中我们可以知道&#xff0c;解码就是在字符串s中由‘1’到‘26’的字符可以转化…...

crm如何做私域运营?

流量获取的挑战日益增加&#xff0c;客户线索成本高、客户资源流失严重、转化率低&#xff0c;因此&#xff0c;私域流量管理已成为关键。 当前挑战 1、公域流量难以整合&#xff1a;外部流量分散&#xff0c;难以有效汇总和沉淀。 2、私域运营体系缺失&#xff1a;缺乏有效沟…...

基于QGIS 3.16.0 的OSM路网矢量范围裁剪实战-以湖南省为例

目录 前言 一、相关数据介绍 1、OMS路网数据 2、路网数据 3、路网图层属性 二、按省域范围进行路网裁剪 1、裁剪范围制定 2、空间裁剪 3、裁剪结果 三、总结 前言 改革开放特别是党的十八大以来&#xff0c;我国公路发展取得了举世瞩目的成就。国家高速公路网由“7 射…...

WPF 手撸插件 八 依赖注入

本文内容大量参考了&#xff1a;https://www.cnblogs.com/Chary/p/11351457.html 而且这篇文章总结的非常好。 1、注意想使用Autofac&#xff0c;Autofac是一个轻量级、‌高性能的依赖注入&#xff08;‌DI&#xff09;‌框架&#xff0c;‌主要用于.NET应用程序的组件解耦和…...

走进低代码报表开发(一):探秘报表数据源

在前文当中&#xff0c;我们对勤研低代码平台的流程设计功能进行了介绍。接下来&#xff0c;让我们一同深入了解在企业日常运营中另一个极为常见的报表功能。在当今数字化时代&#xff0c;高效的报表生成对于企业的决策至关重要。勤研低代码开发平台能够以卓越的性能和便捷的操…...

代理服务器及其原理

代理服务器的代理可以分为正向代理和反向代理&#xff0c;本篇将讲解这两种代理方式的原理&#xff0c;以及对应的功能特点和应用场景。最后还对比和 NAT 和代理服务器的区别。 目录 正向代理 工作原理 功能特点 应用场景 反向代理 基本原理 应用场景 NAT和代理服务器…...

计算机毕业设计选题推荐-养老院管理系统-Java/Python项目实战

✨作者主页&#xff1a;IT毕设梦工厂✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Py…...

免费SSL证书正在逐渐被淘汰,证书部署自动化的发展趋势即将到来!

目录 背景解决方案。1.使用自签证书&#xff08;浏览器报警、免费&#xff09;2.更换支持自签自续的CA机构&#xff08;免费&#xff09;3.付费选择CA机构 免费SSL证书正在逐渐被淘汰&#xff0c;证书部署自动化的发展趋势即将到来免费的SSL证书有以下弊端1.有效期短&#xff1…...

openVX加速-基本概念和简单代码实现

OpenVX 是一个用于计算机视觉和图像处理的跨平台加速标准库&#xff0c;旨在提高在异构平台&#xff08;如 CPU、GPU、DSP 等&#xff09;上的执行效率。OpenVX 提供了一组优化的、可移植的 API&#xff0c;用于加速常见的视觉算法&#xff0c;使开发者能够在不同硬件平台上实现…...

网工内推 | 网络工程师,Base上海,HCIP/HCIE认证优先

01 利宏科技 &#x1f537;招聘岗位&#xff1a;网络工程师 &#x1f537;任职要求 1、有HCIE、HCIP证书 2、做过IDC机房网络建设 3、本科毕业 4、熟悉基本linux命令 5、熟悉山石、华为等防火墙 6、熟悉IPS、WAF等安全设备 7、做过同城灾备机房建设优先 &#x1f537;薪…...

Windows10 如何配置python IDE

Windows10 如何配置python IDE 前言Python直接安装&#xff08;快速上手&#xff09;Step1.找到网址Step2.选择版本&#xff08;非常重要&#xff09;Step3. 安装过程Step4. python测试 Anaconda安装&#xff08;推荐&#xff0c;集成了Spyder和Pycharm的安装&#xff09;Step1…...

Machine Learning: A Probabilistic Perspective 机器学习:概率视角 PDF免费分享

下载链接在博客最底部&#xff01;&#xff01; 之前需要参考这本书&#xff0c;但是大多数博客都是收费才能下载本书。 在网上找了好久才找到免费的资源&#xff0c;浪费了不少时间&#xff0c;在此分享以节约大家的时间。 链接: https://pan.baidu.com/s/1erFsMcVR0A_xT4fx…...

信息学奥赛:青少年编程的高光舞台,通向未来科技的敲门砖

近年来&#xff0c;信息学奥林匹克竞赛&#xff08;NOI&#xff0c;National Olympiad in Informatics&#xff09;逐渐成为众多中学生学习编程、展示才华的热门赛事。这项被誉为“编程天才选拔赛”的竞赛&#xff0c;不仅考验学生的编程能力、算法思维&#xff0c;更是通向名校…...

Android - NDK:在Jni中打印Log信息

在Jni中打印Log信息 1、在配置CMakeLists.txt find_library( # Sets the name of the path variable.log-lib# Specifies the name of the NDK library that# you want CMake to locate.log)# Specifies libraries CMake should link to your target library. You # can link…...

websocket协议解说

WebSocket是一种在单个TCP连接上进行全双工通信的协议。 它为客户端和服务器之间提供了一个持久的连接&#xff0c;允许数据以帧的形式在客户端和服务器之间进行双向传输。 WebSocket协议特别适合需要实时通信的应用&#xff0c;如在线聊天、实时游戏、股票交易、实时监控系统…...

InternVL2-多模态模型原理-多模态模型和组合模型

好的&#xff0c;我会尽量用简单易懂的语言来解释InternVL和InternVL 1.5的工作原理。 InternVL和InternVL 1.5的工作原理 1. 模型结构 InternVL和InternVL 1.5都是由两个主要部分组成&#xff1a;一个视觉模型和一个语言模型。 视觉模型&#xff1a;负责处理图片信息。它的…...

大语言模型之ICL(上下文学习) - In-Context Learning Creates Task Vectors

本文译自 《In-Context Learning Creates Task Vectors》 —— 论文中的作者也在用LLaMA模型&#xff0c;笔者自我感觉拉近和世界顶级人才的距离&#xff0c;哈哈内容较长&#xff0c;如想看结论直接看 摘要、介绍与结论几个章节即可&#xff0c;看细节请看目录索引。经验风险最…...

出现错误消息“ sshd[xxxx]: error: no more session ”的原因是什么?

环境 • 红帽企业 Linux 6 • Red Hat Enterprise Linux 7 • openssh 问题 • SSH 选项的用途是什么MaxAuthTries&#xff0c;MaxSessions和MaxStartups&#xff1f; 解决 MaxAuthTries &#xff1a;指定每个连接允许的最大身份验证尝试次数。一旦失败次数达到此值的一半&…...

代码随想录训练营第29天|控制变量

134. 加油站 class Solution { public:int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {int cur0, total0, start0;for(int i0; i<gas.size(); i){curgas[i]-cost[i];totalgas[i]-cost[i];if(cur<0){starti1;cur0;}}if(start>gas…...

毕业论文选题难?5招帮你轻松搞定选题!

AIPaperGPT&#xff0c;论文写作神器~ https://www.aipapergpt.com/ 你是不是已经为毕业论文的选题愁得头发都要掉光了&#xff1f;每次打开文档&#xff0c;都觉得什么都想写&#xff0c;又好像什么都写不了。选题看起来很简单&#xff0c;但真正开始动手的时候&#xff0c;…...

[QT]记事本项目(信号槽,QT基础控件,QT文件操作,QT关键类,对话框,事件)

一.UI界面搭建 (ui界面使用&#xff0c;界面布局&#xff0c;各控件介绍&#xff0c;界面大小调整) 二.信号槽机制实现文件的打开&#xff0c;保存&#xff0c;退出 (信号槽&#xff0c;QFile文件类&#xff0c;QTextStream类&#xff0c;QFileDialog文件对话框&#xff0…...

redis基本数据结构-hash

这里写自定义目录标题 1. redis的数据结构hash1.1 Hash 数据结构的特点1.2 常见命令1.3 适用示例 2. 常见业务场景2.1 用户信息存储2.1.1 场景2.1.2 优势2.1.3 解决方案2.1.4 代码实现 2.2 购物车管理2.2.1 背景2.2.2 优势2.2.3 解决方案2.2.4 代码实现 3. 注意事项&#xff1a…...

21. 什么是MyBatis中的N+1问题?如何解决?

N1 问题是指在进行一对多查询时&#xff0c;应用程序首先执行一条查询语句获取结果集&#xff08;即 1&#xff09;&#xff0c;然后针对每一条结果&#xff0c;再执行 N 条额外的查询语句以获取关联数据。这个问题通常出现在 ORM 框架&#xff08;如 MyBatis 或 Hibernate&…...

天空卫士项目荣获“2024 IDC 中国20大杰出安全项目 ”奖项 ,实力见证安全守护

9月11日&#xff0c; IDC在上海圆满举办安全风险管控峰会&#xff0c;并现场官宣“2024 IDC中国20大杰出安全项目(CSO20) ”和“2024 IDC中国 CSO名人堂 (十大人物) ” 奖项名单。联通软研院申报的联通邮件系统安全合规建设项目被评为“2024 IDC中国20大杰出安全项目(CSO20) ”…...

Android生成Java AIDL

AIDL:Android Interface Definition Language AIDL是为了实现进程间通信而设计的Android接口语言 Android进程间通信有多种方式&#xff0c;Binder机制是其中最常见的一种 AIDL的本质就是基于对Binder的运用从而实现进程间通信 这篇博文从实战出发&#xff0c;用一个尽可能…...

嵌入式数据库sqlite和rocksdb的介绍以及对比

SQLite 和 RocksDB 都是非常流行的嵌入式数据库系统&#xff0c;但它们的设计理念和应用场景有所不同。下面是对这两个数据库系统的详细介绍以及它们之间的主要区别。 SQLite 简介 SQLite 是一个轻量级的关系数据库管理系统&#xff0c;完全由 C 语言编写而成。它以单一文件…...

数据结构之抽象数据类型(c语言版)

抽象数据类型的定义格式如下&#xff1a; ADT 抽象数据类型名{数据对象&#xff1a;<数据对象的定义>数据关系&#xff1a;<数据关系的定义>基本操作&#xff1a;<基本操作的定义> }ADT 抽象数据类型名 下面以复数为例给出完整的抽象数据类型的定义 ADT C…...