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

一、准备一个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: 80servlet:context-path: /spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/security?serverTimezone=Asia/Shanghaiusername: rootpassword: 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 {@AutowiredXcUserMapper 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 {@Beanpublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}//配置用户信息服务@Beanpublic UserDetailsService userDetailsService() {//这里配置用户信息,这里暂时使用这种方式将用户存储在内存中InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();//在内存创建zhangsan,密码123,分配权限p1manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());//在内存创建lisi,密码456,分配权限p2manager.createUser(User.withUsername("lisi").password("456").authorities("p2").build());return manager;}@Beanpublic PasswordEncoder passwordEncoder() {
//        //密码为明文方式return NoOpPasswordEncoder.getInstance();
//        return new BCryptPasswordEncoder();}//配置安全拦截机制@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/r/**").authenticated()//访问/r开始的请求需要认证通过.anyRequest().permitAll()//其它请求全部放行.and().formLogin().successForwardUrl("/login-success");//登录成功跳转到/login-successhttp.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);@Autowiredprivate XcUserMapper xcUserMapper;@Overridepublic 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;}@Overridepublic String getPassword(){return user.getPassword();}@Overridepublic String getUsername(){return user.getUserName();}/*** 账户是否未过期,过期无法验证*/@Overridepublic boolean isAccountNonExpired(){return true;}/*** 指定用户是否解锁,锁定的用户无法进行身份验证* * @return*/@Overridepublic boolean isAccountNonLocked(){return true;}/*** 指示是否已过期的用户的凭据(密码),过期的凭据防止认证* * @return*/@Overridepublic boolean isCredentialsNonExpired(){return true;}/*** 是否可用 ,禁用的用户不能身份验证* * @return*/@Overridepublic boolean isEnabled(){return true;}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities(){return null;}
}

6、明文登录

6.1、设置明文

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

6.2、测试

在这里插入图片描述

在这里插入图片描述

7、密码加密登录

7.1、设置密码加密

    @Beanpublic 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
{@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 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.1post: 6379

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

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Beanpublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}//配置安全拦截机制@Overrideprotected 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 {@Autowiredprivate AuthenticationManager authenticationManager;@Autowiredprivate RedisCache redisCache;/*** 登录认证的方法* @param xcUser* @return*/@Overridepublic 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生成jwtString jwt = JwtUtils.generateToken(userId);Map<String,String> map = new HashMap<>();map.put("token",jwt);//把完整的用户信息存入redis,userId作为keyredisCache.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 {@Autowiredprivate RedisCache redisCache;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {//获取tokenString token = request.getHeader("token");if (!StringUtils.hasText(token)){//放行filterChain.doFilter(request,response);return;}//解析tokenString 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("用户未登录");}//存入securContextHolderUsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser,null, null);SecurityContextHolder.getContext().setAuthentication(authenticationToken);//放行filterChain.doFilter(request,response);}
}

2、配置认证过滤器

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;@Overrideprotected 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*/@Overridepublic R logout() {//获取SecurityContextHolder中的用户IDUsernamePasswordAuthenticationToken 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;@Overridepublic 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 {@Overridepublic 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">SELECTDISTINCTsm.permsfromsys_user_role surleft join sys_role sr on sur.role_id = sr.role_idLEFT JOIN sys_role_menu srm on sr.role_id = srm.role_idleft join sys_menu sm on srm.menu_id = sm.menu_idwheresm.perms is not nulland 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.mzph.cn/news/16763.shtml

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

相关文章

自定义类型讲解

&#x1f495;痛苦难道是白忍受的吗&#xff1f;&#x1f495; 作者&#xff1a;Mylvzi 文章主要内容&#xff1a;自定义类型讲解 一.结构体 定义&#xff1a; 数组&#xff1a;多组相同类型元素的集合 结构体&#xff1a;多组不同类型元素的集合-->管理多组不同类型数据…

计算机视觉实验:人脸识别系统设计

实验内容 设计计算机视觉目标识别系统&#xff0c;与实际应用有关&#xff08;建议&#xff1a;最终展示形式为带界面可运行的系统&#xff09;&#xff0c;以下内容选择其中一个做。 1. 人脸识别系统设计 (1) 人脸识别系统设计&#xff08;必做&#xff09;&#xff1a;根据…

tinkerCAD案例:24. Ruler - Measuring Lengths 标尺 -量勺

tinkerCAD案例&#xff1a;24. Ruler - Measuring Lengths 标尺 - 测量长度 Project Overview: 项目概况&#xff1a; A machine shop, where any idea can become a reality, can cost millions and million of dollars. Still, the most important tool in the shop is the…

vue-cli4升级到vue-cli5的踩坑记录

前言 最近对部分项目升级了vue-cli脚手架&#xff0c;记录一下 问题一&#xff1a; scss/less/css中无法引入public下的静态资源 问题描述 在样式文件中使用静态资源路径导致编译无法通过 错误信息如下&#xff1a; Module not found: Error: Cant resolve /img/login/lo…

小研究 - 主动式微服务细粒度弹性缩放算法研究(二)

微服务架构已成为云数据中心的基本服务架构。但目前关于微服务系统弹性缩放的研究大多是基于服务或实例级别的水平缩放&#xff0c;忽略了能够充分利用单台服务器资源的细粒度垂直缩放&#xff0c;从而导致资源浪费。为此&#xff0c;本文设计了主动式微服务细粒度弹性缩放算法…

Android 面试题 应用程序结构 十

&#x1f525; Intent 传递数据 &#x1f525; Activity、Service、BroadcastReceiver之间的通信载体 Intent 来传递数据。而ContentProvider则是共享文件。 Intent可传递的数据类型&#xff1a; a. 8种基本数据类型&#xff08;boolean byte char short int long float double…

如何配置一个永久固定的公网TCP地址来SSH远程树莓派?

文章目录 如何配置一个永久固定的公网TCP地址来SSH远程树莓派&#xff1f;前置条件命令行使用举例&#xff1a;修改cpolar配置文件 1. Linux(centos8)安装redis数据库2. 配置redis数据库3. 内网穿透3.1 安装cpolar内网穿透3.2 创建隧道映射本地端口 4. 配置固定TCP端口地址4.1 …

第1集丨Vue 江湖 —— Hello Vue

目录 一、简介1.1 参考网址1.2 下载 二、Hello Vue2.1 创建页面2.2 安装Live Server插件2.4 安装 vue-devtools2.5 预览效果 一、简介 Vue&#xff08;读音 /vjuː/, 类似于 view&#xff09; 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是&#xff0c;Vue 被设…

app自动化测试之Appium问题分析及定位

使用 Appium 进行测试时&#xff0c;会产生大量日志&#xff0c;一旦运行过程中遇到报错&#xff0c;可以通过 Appium 服务端的日志以及客户端的日志分析排查问题。 Appium Server日志-开启服务 通过命令行的方式启动 Appium Server&#xff0c;下面来分析一下启动日志&#…

使用web-view实现网页端和uni-app端是数据传输

要实现这个功能 第一步&#xff1a;要在vue的public文件夹下面引入 <script type"text/javascript" src"https://js.cdn.aliyun.dcloud.net.cn/dev/uni-app/uni.webview.1.5.2.js"></script> 第二步&#xff1a;建立一个新的空的uni-app项目…

DHorse v1.3.0 发布,基于k8s的发布平台

综述 DHorse是一个简单易用、以应用为中心的云原生DevOps系统&#xff0c;具有持续集成、持续部署、微服务治理等功能&#xff0c;无需安装依赖Docker、Maven、Node等环境即可发布Java、Vue、React应用&#xff0c;主要特点&#xff1a;部署简单、操作简洁、功能快速。 新增特…

Apache Doris 巨大飞跃:存算分离新架构

作者&#xff1a;马如悦 Apache Doris 创始人 历史上&#xff0c;数据分析需求的不断提升&#xff08;更大的数据规模、更快的处理速度、更低的使用成本&#xff09;和计算基础设施的不断进化&#xff08;从专用的高端硬件、到低成本的商用硬件、到云计算服务&#xff09;&…

JPEG有损图像压缩编码器(附源码)

概述 一个基本由自己实现的JPEG有损图像压缩编码器&#xff0c;基于JFIF&#xff08;JPEG文件交换格式&#xff09;标准&#xff1a; 色彩空间转换&#xff08;RGB to YUV&#xff09;色度抽样&#xff08;采样因子4:2:0&#xff09;MCU分块&#xff08;16x16的最小编码单元&…

多模态第2篇:MMGCN代码配置

一、Windows环境 1.创建并激活虚拟环境 #创建虚拟环境命名为mmgcn&#xff0c;指定python版本为3.8 conda create -n mmgcn python3.8 #激活虚拟环境 conda activate mmgcn2.安装pytorch #torch2.0.0 cu118 pip install torch2.0.0cu118 torchvision0.15.1cu118 torchaudio…

PACS系统源码:支持三维重建功能、集成放射科管理RIS系统、图文报告编辑、打印、多级审核机制

PACS系统源码 PACS系统是以最新的IT技术为基础&#xff0c;遵循医疗卫生行业IHE/DICOM3.0和HL7标准&#xff0c;开发的多功能服务器和阅片系统。通过简单高性能的阅片功能&#xff0c;支持繁忙时的影像诊断业务&#xff0c;拥有保存影像的院内Web传输及离线影像等功能&#xf…

【雕爷学编程】MicroPython动手做(11)——搭建掌控板IDE开发环境四种

为了能够打好基础&#xff0c;系统学习MicroPython&#xff0c;特地入手了二块掌控板 知识点&#xff1a;什么是掌控板&#xff1f; 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片&#xff0c;支持WiFi和蓝牙双模通…

【Docker 学习笔记】Docker架构及三要素

文章目录 一、Docker 简介二、Docker 架构1. Docker 客户端和服务器2. Docker 架构图3. Docker 运行流程图 三、Docker 三要素1. 镜像&#xff08;Image&#xff09;2. 容器&#xff08;Container&#xff09;3. 仓库&#xff08;Repository&#xff09; 一、Docker 简介 Dock…

2.4 传统经验光照模型详解

一、光照模型 光照模型&#xff08;illumination model&#xff09;&#xff0c;也称为明暗模型&#xff0c;用于计算物体某点处的光强&#xff08;颜色值&#xff09;。从算法理论基础而言&#xff0c;光照模型分为两类&#xff1a;一种是基于物理理论的&#xff0c;另一种是…

【MATLAB第61期】基于MATLAB的GMM高斯混合模型回归数据预测

【MATLAB第61期】基于MATLAB的GMM高斯混合模型回归数据预测 高斯混合模型GMM广泛应用于数据挖掘、模式识别、机器学习和统计分析。其中&#xff0c;它们的参数通常由最大似然和EM算法确定。关键思想是使用高斯混合模型对数据&#xff08;包括输入和输出&#xff09;的联合概率…

<Doc>Windows常见的doc命令

一&#xff1a;管理员身份运行cmd命令&#xff1a; 方式一&#xff1a;搜索框输入cmd&#xff0c;回车&#xff0c;点击&#xff1a;以管理员身份运行 出现如图所示&#xff1a; 方式二&#xff1a;快捷键运行方式&#xff1a; 1.按winr&#xff0c;在运行窗口中输入cmd。 …