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

重庆网络营销网站建设销售网页设计制作员

重庆网络营销网站建设销售,网页设计制作员,wordpress漏洞改密码,建设网站的虚拟机配置文章目录 Spring Security介绍Spring Security案例1、快速搭建一个springboot工程2、导入SpringSecurity整合springboot工程3、认证3.1、登录流程校验3.2、入门案例的原理3.3、实现思路3.4、实现认证流程#xff08;自定义#xff09;3.5、正式实现3.5.1 实现数据库的校验3.5… 文章目录 Spring Security介绍Spring Security案例1、快速搭建一个springboot工程2、导入SpringSecurity整合springboot工程3、认证3.1、登录流程校验3.2、入门案例的原理3.3、实现思路3.4、实现认证流程自定义3.5、正式实现3.5.1 实现数据库的校验3.5.2 密码加密存储3.5.3 自定义登陆接口实现3.5.4 自定义实现认证过滤器3.5.5 退出登录 4、授权4.1 授权基本流程4.2 授权实现不结合数据库4.2.1 限制访问资源所需权限4.2.2 封装权限信息 4.3 授权实现结合数据库4.3.1 设计数据库表4.3.2 代码实现4.3.3 测试 5、自定义访问失败处理5.1、自定义实现类5.2、配置给SpringSecurity 6、跨域问题7、扩展7.1、自定义权限校验方法7.2、基于配置的权限控制 8、 demo源码9、特别鸣谢 Spring Security介绍 要对Web资源进行保护最好的办法莫过于Filter 要想对方法调用进行保护最好的办法莫过于AOP。 Spring Security进行认证和鉴权的时候,就是利用的一系列的Filter来进行拦截的。 如图所示一个请求想要访问到API就会从左到右经过蓝线框里的过滤器其中绿色部分是负责认证的过滤器蓝色部分是负责异常处理橙色部分则是负责授权。进过一系列拦截最终访问到我们的API。 这里面我们只需要重点关注两个过滤器即可 UsernamePasswordAuthenticationFilter负责登录认证 FilterSecurityInterceptor负责权限授权。 一般Web应用的需要进行认证和授权。 ​ 认证Authentication验证当前访问系统的是不是本系统的用户并且要确认具体是哪个用户 ​ 授权Authorization经过认证后判断当前用户是否有权限进行某个操作 ​ 而认证和授权就是SpringSecurity作为安全框架的核心功能。 说明Spring Security的核心逻辑全在这一套过滤器中过滤器里会调用各种组件完成功能掌握了这些过滤器和组件你就掌握了Spring Security这个框架的使用方式就是对这些过滤器和组件进行扩展。 Spring Security案例 1、快速搭建一个springboot工程 此步骤省略 建议整合knif4j方便调试 访问接口文档http://localhost:8081/doc.html端口为自己设置的端口默认是8080 springboot工程搭建完成 2、导入SpringSecurity整合springboot工程 导入SpringSecurity依赖版本跟随boot版本 dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-security/artifactId /dependency 重新启动项目进行测试访问路径http://localhost:8081/ayo/hhy/1 这时我们可以看到当我们访问我们的接口的时候就会自动跳转到一个SpringSecurity的默认登陆页面(后期是需要整合到自己系统的登录页面来做认证 这时候需要我们登录才可以进行访问我们可以看到控制台有一串字符串其实那就是SpringSecurity初始化生成给我的密码 用户名默认为 user 密码在控制台打印出来了 输入用户和密码登录后就可以正常访问接口了 3、认证 3.1、登录流程校验 核心是通过用户输入的用户名和密码去和数据库中的数据进行比对如果比对无误 那么就会使用jwt工具根据用户名和密码去生成一个token传给前端存储起来 在登录过后用户想要访问别的资源都要在请求头里面携带token后端会对前端请求的token拿到并且解析出来用户的id 根据用户的id获取相关的信息来判断用户是否有访问资格如果有则访问目标资源返回给前端若没有则返回错误信息无权限访问 3.2、入门案例的原理 前后端进行认证的流程 具体流程 在上面的案例整合springSecurity进入默认的登录页面进行登录的时候是查询内存中的用户名和密码的后期我们做项目肯定是要改成去数据库里面查询用户名和密码比对 其中 Authentication接口它的实现类表示当前访问系统的用户封装了用户相关信息。 AuthenticationManager接口定义了认证Authentication的方法。 UserDetailsService接口加载用户特定数据的核心接口。里面定义了一个根据用户名查询用户信息的方法。 UserDetails接口提供核心用户信息。通过UserDetailsService根据用户名获取处理的用户信息要封装成UserDetails对象返回。然后将这些信息封装到Authentication对象中。 3.3、实现思路 登录流程 登录认证成功之后根据用户名和密码通过jwt生成一个token返回给前端前端每次请求都会携带token到请求里面同时将token存入redis其中用户id作为key用户信息作为value主要是为了不让后续的校验流程频繁的查询数据库这里的UserDetailsService默认是去查内存来认证用户名和密码的需要自定义UserDetailsService这个接口去查自己的数据库的数据进行认证我们可以自定义一个UserDetailsService,让SpringSecurity使用我们的UserDetailsService。我们自己的UserDetailsService可以从数据库中查询用户名和密码。 校验流程 获取token解析token获取到其中的userId 根据userId从redis中获取用户信息存入SecurityContextHolder。 前端每次发送请求的时候请求头都会携带token。后端从redis根据key去拿到token并且通过jwt解析出用户的信息。通过用户的信息查询用户可以使用的权限然后返回给前端去访问能访问的资源 3.4、实现认证流程自定义 准备工作 1. 引入依赖jjwt、reids、fastjsonmysqlmybatis-plus !--redis--dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId/dependency !-- fastjson2--dependencygroupIdcom.alibaba.fastjson2/groupIdartifactIdfastjson2/artifactIdversion2.0.25/version/dependency !--jjwt--dependencygroupIdio.jsonwebtoken/groupIdartifactIdjjwt/artifactIdversion0.9.1/version/dependencydependencygroupIdcom.baomidou/groupIdartifactIdmybatis-plus-boot-starter/artifactIdversion3.4.2/version /dependencydependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactId /dependency 2. 添加相关配置 redis建议直接使用stringRedisTemplate如果使用redisTemplate需要对数据进行序列化做相关的配置 参考链接redisTemplate和stringRedisTemplate对比、redisTemplate几种序列化方式比较 因为是前后端分离所以需要一统一结果返回类 R类不唯一能用就行 Data public class RT implements Serializable {private Integer code; //编码1成功0和其它数字为失败private String msg; //错误信息private T data; //数据private Map map new HashMap(); //动态数据public static T RT success(T object) {RT r new RT();r.data object;r.code 1;return r;}public static T RT success(String msg,T object) {RT r new RT();r.data object;r.code 1;r.msg msg;return r;}public static T RT error(String msg) {R r new R();r.msg msg;r.code 0;return r;}public static T RT success(String msg) {R r new R();r.msg msg;r.code 1;return r;}public RT add(String key, Object value) {this.map.put(key, value);return this;}} 导入jwt工具类 /*** JWT工具类*/ public class JwtUtil {//有效期为public static final Long JWT_TTL 60 * 60 *1000L;// 60 * 60 *1000 一个小时//设置秘钥明文public static final String JWT_KEY qx;public static String getUUID(){String token UUID.randomUUID().toString().replaceAll(-, );return token;}/*** 生成jtw jwt加密* param subject token中要存放的数据json格式* return*/public static String createJWT(String subject) {JwtBuilder builder getJwtBuilder(subject, null, getUUID());// 设置过期时间return builder.compact();}/*** 生成jtw jwt加密* param subject token中要存放的数据json格式* param ttlMillis token超时时间* return*/public static String createJWT(String subject, Long ttlMillis) {JwtBuilder builder getJwtBuilder(subject, ttlMillis, getUUID());// 设置过期时间return builder.compact();}/*** 创建token jwt加密* param id* param subject* param ttlMillis* return*/public static String createJWT(String id, String subject, Long ttlMillis) {JwtBuilder builder getJwtBuilder(subject, ttlMillis, id);// 设置过期时间return builder.compact();}private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {SignatureAlgorithm signatureAlgorithm SignatureAlgorithm.HS256;SecretKey secretKey generalKey();long nowMillis System.currentTimeMillis();Date now new Date(nowMillis);if(ttlMillisnull){ttlMillisJwtUtil.JWT_TTL;}long expMillis nowMillis ttlMillis;Date expDate new Date(expMillis);return Jwts.builder().setId(uuid) //唯一的ID.setSubject(subject) // 主题 可以是JSON数据.setIssuer(sg) // 签发者.setIssuedAt(now) // 签发时间.signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥.setExpiration(expDate);}public static void main(String[] args) throws Exception {//jwt加密String jwt createJWT(123456);//jwt解密Claims claims parseJWT(jwt);String subject claims.getSubject();System.out.println(subject);System.out.println(jwt);}/*** 生成加密后的秘钥 secretKey* return*/public static SecretKey generalKey() {byte[] encodedKey Base64.getDecoder().decode(JwtUtil.JWT_KEY);SecretKey key new SecretKeySpec(encodedKey, 0, encodedKey.length, AES);return key;}/*** jwt解密** param jwt* return* throws Exception*/public static Claims parseJWT(String jwt) throws Exception {SecretKey secretKey generalKey();return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody();}} 导入实体类 /*** 用户表(User)实体类***/ Data AllArgsConstructor NoArgsConstructor TableName(sys_user) public class User implements Serializable {private static final long serialVersionUID -40356785423868312L;/*** 主键*/TableIdprivate Long id;/*** 用户名*/private String userName;/*** 昵称*/private String nickName;/*** 密码*/private String password;/*** 账号状态0正常 1停用*/private String status;/*** 邮箱*/private String email;/*** 手机号*/private String phonenumber;/*** 用户性别0男1女2未知*/private String sex;/*** 头像*/private String avatar;/*** 用户类型0管理员1普通用户*/private String userType;/*** 创建人的用户id*/private Long createBy;/*** 创建时间*/private Date createTime;/*** 更新人*/private Long updateBy;/*** 更新时间*/private Date updateTime;/*** 删除标志0代表未删除1代表已删除*/private Integer delFlag; } 数据库建表并且配置数据库 CREATE TABLE sys_user (id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键,user_name varchar(64) NOT NULL DEFAULT NULL COMMENT 用户名,nick_name varchar(64) NOT NULL DEFAULT NULL COMMENT 昵称,password varchar(64) NOT NULL DEFAULT NULL COMMENT 密码,status char(1) DEFAULT 0 COMMENT 账号状态0正常 1停用,email varchar(64) DEFAULT NULL COMMENT 邮箱,phonenumber varchar(32) DEFAULT NULL COMMENT 手机号,sex char(1) DEFAULT NULL COMMENT 用户性别0男1女2未知,avatar varchar(128) DEFAULT NULL COMMENT 头像,user_type char(1) NOT NULL DEFAULT 1 COMMENT 用户类型1代表普通用户0代表管理员,create_by bigint(20) DEFAULT NULL COMMENT 创建人的用户id,create_time datetime DEFAULT NULL COMMENT 创建时间,update_by bigint(20) DEFAULT NULL COMMENT 更新人,update_time datetime DEFAULT NULL COMMENT 更新时间,del_flag int(11) DEFAULT 0 COMMENT 删除标志0代表未删除1代表已删除,PRIMARY KEY (id) ) ENGINEInnoDB AUTO_INCREMENT14787164048663 DEFAULT CHARSETutf8mb4 COMMENT用户表; datasource:druid:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/security?characterEncodingutf-8useSSLfalseusername: rootpassword: root 定义mapper、service、serviceImpl并测试mapper能正常使用 省略可以使用代码生成器 3.5、正式实现 3.5.1 实现数据库的校验 创建一个类实现UserDetailsService接口重写其中的loadUserByUsername方法。增加用户名从数据库中查询用户信息 - 这里只是查了用户信息对于用户权限还没有查这部分是属于授权部分的内容因为最后是把用户信息和权限信息封装成UserDetails进行返回的 Service public class UserDetailsServiceImpl implements UserDetailsService {Autowiredprivate UserService userService;Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//查询用户信息User user userService.getOne(new LambdaUpdateWrapperUser().eq(User::getUserName, username));//如果没有查询到用户就抛出异常if (Objects.isNull(user)) {throw new CustomException(用户名或密码错误);}//TODO 查询对应的权限信息//把数据封装成UserDetails返回return new LoginUser(user);} } 自定义一个实现UserDetails的LoginUser类实现接口的方法用来接收存储数据用户信息和权限信息 将用户属性注入进来更改里面获取用户名和密码的get方法使其获得user的信息 Data NoArgsConstructor AllArgsConstructor public class LoginUser implements UserDetails {private User user;//这里先不管权限信息这个集合Overridepublic Collection? extends GrantedAuthority getAuthorities() {return null;} //获取用户的用户的密码Overridepublic String getPassword() {return user.getPassword();}Overridepublic String getUsername() {return user.getUserName();}Overridepublic boolean isAccountNonExpired() {return true;}Overridepublic boolean isAccountNonLocked() {return true;}Overridepublic boolean isCredentialsNonExpired() {return true;}Overridepublic boolean isEnabled() {return true;} } 此次再次启动服务访问任意接口这个时候走的就是查我们自己的数据库了可以在实现类里面打断点测试 此时后端控制台就不会打印出在内存中的密码了因为我们重写了UserDetailsService 接口的loadUserByUsername程序走我们自己的方法。 注意点击登录后这里会出现一个问题 当输入正确的用户名和密码还是会显示密码错误 控制台打印的错误信息可以看出 因为我们数据库的密码是明文保存的而PasswordEncoder方法其实是将数据库的加密的密码解密后再和用户输入的明文密码进行比对的 处理办法在 Spring Security 中{noop} 前缀告诉系统直接将密码存储为明文即未经过哈希或其他加密算法处理。 再次登录测试能够成功访问接口方法 3.5.2 密码加密存储 实际项目中我们不会把密码明文存储在数据库中。 默认使用的PasswordEncoder要求数据库中的密码格式为{id}password它会根据id去判断密码的加密方式。但是我们一般不会采用这种方式所以需要替换PasswordEncoder。\我们一般使用SpringSecurity为我们提供的BCryptPasswordEncoder。我们只需要把BCryptPasswordEncoder对象注入Spring容器SpringSecurity就会使用该PasswordEncoder来进行密码校验。 我们可以定义一个SpringSecurity的配置类SpringSecurity要求这个配置类要继承WebSecurityConfigurerAdapter。 Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter {//创建BCryptPasswordEncoder注入容器Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();} }相当于将原来的PasswordEncoder 中的方法覆盖掉转而执行BCryptPasswordEncoder中的方法 其中BCryptPasswordEncoder对象中的三个方法分别作用是 encode 用于对密码进行加密matches用于判断给定的原始密码与已加密的密码是否匹配upgradeEncoding用于升级密码的加密方式 这个时候数据库里面的用户密码必须是加密过的密码这样在进行认证的时候根据用户名查询到加密的密码然后再根据passwordEncoder中的密码匹配方法来判断密码是否正确。若数据库还是明文密码那么肯定是会密码错误认证失败的 咱可以先把数据库的明文密码加密放回原用户编写Test方法加密用户密码 Testpublic void testPasswordEncoder() {BCryptPasswordEncoder passwordEncoder new BCryptPasswordEncoder();String encode passwordEncoder.encode(123456);System.out.println(encode);}得到加密的密码填入数据库 重启系统登录测试登陆成功正常访问接口 3.5.3 自定义登陆接口实现 其实质就是不让登录走整合SpringSecurity默认的登录页面了而是走我们自己定义的登录接口 接下我们需要自定义登陆接口然后让SpringSecurity对这个接口放行,让用户访问这个接口的时候不用登录也能访问。 ​ 在接口中我们通过AuthenticationManager的authenticate方法来进行用户认证,所以需要在SecurityConfig中配置把AuthenticationManager注入容器。 ​ 认证成功的话要生成一个jwt放入响应中返回。并且为了让用户下回请求时能通过jwt识别出具体的是哪个用户我们需要把用户信息存入redis可以把用户id作为key。 创建LoginController 登录接口 其中LoginService 接口和实现接口类省略 RestController RequestMapping(/user) public class LoginController {Autowiredprivate LoginService loginService;PostMapping(/login)public R login(RequestBody User user){return loginService.login(user);}那么我们可以在loginService实现类中编写login方法 其中验证是通过通过AuthenticationManager的authenticate方法来进行用户认证,需要在SecurityConfig中配置把AuthenticationManager注入容器 并且进行一个configure的配置才能使用 ·SecurityConfig· Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter {//创建BCryptPasswordEncoder注入容器Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}//把AuthenticationManager注入容器BeanOverridepublic 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(/user/login).anonymous()// 除上面外的所有请求全部需要鉴权认证.anyRequest().authenticated();} 这个时候就可以编写登录接口了 通过UsernamePasswordAuthenticationToken获取用户名和密码 2.AuthenticationManager委托机制对authenticationToken 进行用户认证如果认证没有通过给出对应的提示如果认证通过使用user生成jwt jwt存入R返回如果认证通过拿到这个当前登录用户信息把完整的用户信息存入redis userid为key 用户信息为value 此时只有用户信息还没有包括权限信息后续授权会完善 Service public class LoginServiceImpl implements LoginService {Autowiredprivate UserService userService;//认证委托Autowiredprivate AuthenticationManager authenticationManager;Autowiredprivate StringRedisTemplate redisTemplate;Overridepublic R login(User user) { //通过UsernamePasswordAuthenticationToken获取用户名和密码UsernamePasswordAuthenticationToken authenticationTokennew UsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword());//AuthenticationManager委托机制对authenticationToken 进行用户认证Authentication authenticate authenticationManager.authenticate(authenticationToken);//如果认证没有通过给出对应的提示if (Objects.isNull(authenticate)){throw new RuntimeException(登录失败);}//如果认证通过使用user生成jwt jwt存入R 返回//如果认证通过拿到这个当前登录用户信息LoginUser loginUser (LoginUser) authenticate.getPrincipal();//获取当前用户的useridString userid loginUser.getUser().getId().toString();String jwt JwtUtil.createJWT(userid);MapString, String map new HashMap();map.put(token,jwt);//把完整的用户信息存入redis userid为key 用户信息为value 将对象转换为 JSON 字符串 存入redisString jsonString JSONUtil.toJsonStr(loginUser);redisTemplate.opsForValue().set(loginuserid, jsonString );return R.success(登陆成功,map);} }重启服务进行登录测试建议打断点一步步测试数据 这里我们用postman进行登录测试 user/login被放行可以不用登录认证直接访问 输入urllocalhost:8081/user/login 通过json传输用户信息 结果返回 并且查看reids 此时认证成功~ 3.5.4 自定义实现认证过滤器 我们需要自定义一个过滤器这个过滤器会去获取请求头中的token对token进行解析取出其中的userid。 使用userid去redis中获取对应的LoginUser对象。然后封装Authentication对象存入SecurityContextHolder 定义过滤类JwtAuthenticationTokenFilter 流程 过滤器拦截到请求并且从请求头中拿到token根据拿到的token根据jwt工具类解析得到用户的IDuserId根据userId也就是之前reids存token的key去redis拿到用户信息若用户信息不为null代表该用户没有登录过抛异常若能拿到用户信息封装Authentication对象存入SecurityContextHolder并且获取权限信息封装到Authentication中(这里属于授权过程暂不实现放行 封装Authentication对象:包括两部分 用户信息用户权限信息 Component public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {Autowiredprivate StringRedisTemplate redisTemplate;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 {Claims claims JwtUtil.parseJWT(token);userid claims.getSubject();} catch (Exception e) {e.printStackTrace();throw new CustomException(token非法);}//从redis中获取用户信息String redisKey login: userid;//将json转换为对象类String json redisTemplate.opsForValue().get(redisKey);LoginUser loginUser JSONUtil.toBean(json, LoginUser.class);if(Objects.isNull(loginUser)){throw new RuntimeException(用户未登录);}//封装Authentication对象存入SecurityContextHolder//TODO 获取权限信息封装到Authentication中UsernamePasswordAuthenticationToken authenticationToken new UsernamePasswordAuthenticationToken(loginUser,null,null);SecurityContextHolder.getContext().setAuthentication(authenticationToken);//放行filterChain.doFilter(request, response);} } 把token校验过滤器添加到过滤器链中注入到配置类SecurityConfig加入过滤器链 //把token校验过滤器注入配置类AutowiredJwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;//把token校验过滤器添加到过滤器链中并且添加到UsernamePasswordAuthenticationFilter之前http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter {//创建BCryptPasswordEncoder注入容器Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}//把AuthenticationManager注入容器BeanOverridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}//把token校验过滤器注入配置类AutowiredJwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;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();//把token校验过滤器添加到过滤器链中http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);}} 测试 编写一个接口来判断登录过后是否可以拿到token并且解析出用户信息 测试1不登录直接访问访问路径localhost:8081/ayo/coo 断点调试JwtAuthenticationTokenFilter 此时过滤器拦截到请求发现请求头里面没有token代表用户没有登录直接放行retrun 认证失败 测试2登录后设置请求头登录生成的token再次访问 访问路径localhost:8081/ayo/coo 记录登录成功的token值 在访问路径localhost:8081/ayo/coo时在请求头加上登录过后生成的token 此时断点调试JwtAuthenticationTokenFilter成功从请求头拿到token 然后根据token解析到用户的iduserId根据userId从redis取出用户的信息然后将用户的信息和权限信息封装成Authentication对象存入SecurityContextHolder SecurityContextHolder SecurityContextHolder 类提供了一种方便的方式来获取当前用户的安全信息如身份验证信息和角色信息。它是一个线程本地的存储可以在应用程序的不同层级中访问 返回此次调用接口的数据 3.5.5 退出登录 流程 执行退出登录的请求时请求头携带token进入doFilterInternal过滤器解析token得到用户userId根据userId从redis中拿到用户信息并且将用户信息封装成 Authentication对象并且存入SecurityContextHolder SecurityContextHolder.getContext().setAuthentication(authenticationToken); 执行controller的方法—service中的logout方法 从SecurityContextHolder中获取用户的userid根据userid找到redis对应值进行删除 controller层 GetMapping(/logout)public R login(){return loginService.logout();}service实现类 /*** 注销接口* return*/Overridepublic R logout() {//从SecurityContextHolder中的useridUsernamePasswordAuthenticationToken authentication (UsernamePasswordAuthenticationToken)SecurityContextHolder.getContext().getAuthentication();LoginUser loginUser (LoginUser) authentication.getPrincipal();Long userid loginUser.getUser().getId();//根据userid找到redis对应值进行删除redisTemplate.delete(login:userid);return R.success(注销成功);}其中的核心是通过过滤器将用户的userId放入SecurityContextHolder中中了在注销登录的时候可以直接拿到userId再去删除redis中的token信息 注意这个时候再去访问其他接口的时候发送过来的请求的请求头所携带的还是之前的登录时的token这个时候进入过滤器的时候可以根据token获得用户的userId但是在根据userId去redis获得用户信息的时候是获取不到的注销的时候已经删除该用户对应的信息抛出未登录异常. 控制台报错 4、授权 权限系统的作用让不同的用户可以使用不同的功能。 实现不能只依赖前端去判断用户的权限来选择显示哪些菜单哪些按钮。因为让前端判断权限别人可以根据对应功能的接口地址直接发送请求来实现相关的操作。 所以我们还需要在后台进行用户权限的判断判断当前用户是否有响应的权限必须具有所需权限才能进行相应的操作。 4.1 授权基本流程 在SpringSecurity中会使用默认的FilterSecurityInterceptor来进行权限校验。在FilterSecurityInterceptor中会从SecurityContextHolder获取其中的Authentication然后获取其中的权限信息。当前用户是否拥有访问当前资源所需的权限。 所以我们在项目中需要把当前登录用户的权限信息也存入Authentication然后设置我们的资源所需要的权限即可。 4.2 授权实现不结合数据库 4.2.1 限制访问资源所需权限 SpringSecurity为我们提供了基于注解的权限控制方案这也是我们项目中主要采用的方式我们可以使用注解去指定访问对应的资源所需的权限。 使用前先在SpringSecurity配置类上开启相关配置 EnableGlobalMethodSecurity(prePostEnabled true)这样就代表开启了使用注解去指定访问权限的功能例如 ApiOperation(value 权限测试接口)PreAuthorize(hasAuthority(tset))GetMapping(/auth)public R auth(){return R.success(权限测试接口访问成功);}PreAuthorize(“hasAuthority(‘tset’)”) 的含义是只有具有 tset 权限的用户才能够调用被注解的方法。 hasAuthority() 是 Spring Security 提供的一个表达式用于检查用户是否具有指定的权限。在这种情况下只有当用户拥有 tset 权限权限名称为 tset时才能访问被注解的方法。 4.2.2 封装权限信息 封装权限信息的步骤 首先在用户进行认证登录成功之后就会在UserDetailsService接口的实现类方法loadUserByUsername去获得用户信息和权限信息把数据封装成UserDetails返回。 这里的权限信息集合我们先写死成“test”后续是从数据库根据用户查出来的权限信息集合 此时LoginUser需要封装user和AuthList两个对象所以需要改造登录封装类LoginUser 改造登录封装类LoginUser 就是将登录认证后查到的权限集合封装到LoginUser默认框架是不会去调用我们自己定义的权限集合的需要将自己传入的权限集合通过getAuthorities()方法封装成一个属性为SimpleGrantedAuthority的权限集合供框架做权限控制。 小细节因为将数据存到redis是会序列化的但是SimpleGrantedAuthority序列化会报错所以存入权限信息的时候存入permissions就可以了string类型 使用注解 JSONField(serialize false) 让其不序列化 然后再过滤器中根据每次请求携带的token进行登录的时候所有的用户信息和权限信息都在redis中解析用户id然后根据用户id去redis查询用户信息和权限信息将权限信息和用户信息封装到Authentication中。 断点测试 在进行登录的时候通过委托认证中的loadUserByUsername方法将权限信息和用户信息封装成LoginUser对象。在登录验证通过后会将完整的信息存入redis中 reids数据如下 这里为什么redis显示authorities没有数据呢其实数据已经存进去了只是在属性上加上了注解JSONField(serialize false)使其不序列化展示出来数据还是有的 访问带权限的接口时 过滤器会根据解析token的用户id去redis查询用户的所有信息封装到Authentication对象交给框架去做方法授权其中里面包括了权限信息这里举例test 能够成功访问 同理如果这个controller设置的权限通过不了框架的授权验证肯定是不能访问的会报错。 、、、、、、、、更新中 4.3 授权实现结合数据库 因为之前实现授权是将权限集合写死了我们通常在项目中权限应该是从数据库根据用户对应的角色中查出来的如下图 RBAC权限模型也就是上图 RBAC权限模型Role-Based Access Control即基于角色的权限控制。这是目前最常被开发者使用也是相对易用、通用的权限模型。 4.3.1 设计数据库表 需要设置角色表、权限表以及用户角色关联表角色权限关联表 CREATE TABLE sys_user (id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键,user_name varchar(64) NOT NULL DEFAULT NULL COMMENT 用户名,nick_name varchar(64) NOT NULL DEFAULT NULL COMMENT 昵称,password varchar(64) NOT NULL DEFAULT NULL COMMENT 密码,status char(1) DEFAULT 0 COMMENT 账号状态0正常 1停用,email varchar(64) DEFAULT NULL COMMENT 邮箱,phonenumber varchar(32) DEFAULT NULL COMMENT 手机号,sex char(1) DEFAULT NULL COMMENT 用户性别0男1女2未知,avatar varchar(128) DEFAULT NULL COMMENT 头像,user_type char(1) NOT NULL DEFAULT 1 COMMENT 用户类型1代表普通用户0代表管理员,create_by bigint(20) DEFAULT NULL COMMENT 创建人的用户id,create_time datetime DEFAULT NULL COMMENT 创建时间,update_by bigint(20) DEFAULT NULL COMMENT 更新人,update_time datetime DEFAULT NULL COMMENT 更新时间,del_flag int(11) DEFAULT 0 COMMENT 删除标志0代表未删除1代表已删除,PRIMARY KEY (id) ) ENGINEInnoDB AUTO_INCREMENT14787164048663 DEFAULT CHARSETutf8mb4 COMMENT用户表;CREATE TABLE sys_menu (id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 菜单ID,menu_name varchar(50) NOT NULL COMMENT 菜单名称,path varchar(200) DEFAULT COMMENT 路由地址,component varchar(255) DEFAULT NULL COMMENT 组件路径,visible char(1) DEFAULT 0 COMMENT 菜单状态0显示 1隐藏,status char(1) DEFAULT 0 COMMENT 菜单状态0正常 1停用,perms varchar(100) DEFAULT NULL COMMENT 权限标识,icon varchar(100) DEFAULT # COMMENT 菜单图标,create_by bigint(20) DEFAULT NULL COMMENT 创建者,create_time datetime DEFAULT NULL COMMENT 创建时间,update_by bigint(20) DEFAULT NULL COMMENT 更新者,update_time datetime DEFAULT NULL COMMENT 更新时间,remark varchar(500) DEFAULT COMMENT 备注,del_flag int(11) DEFAULT 0 ,PRIMARY KEY (id) ) ENGINEInnoDB AUTO_INCREMENT2029 DEFAULT CHARSETutf8mb4 COMMENT菜单权限表;CREATE TABLE sys_role (id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 角色ID,name varchar(128) DEFAULT NULL COMMENT 角色名称,role_key varchar(100) DEFAULT NULL COMMENT 角色权限字符串,status char(1) NOT NULL COMMENT 角色状态0正常 1停用,del_flag int(1) DEFAULT 0 COMMENT 删除标志0代表存在 1代表删除,create_by bigint(20) DEFAULT NULL COMMENT 创建者,create_time datetime DEFAULT NULL COMMENT 创建时间,update_by bigint(20) DEFAULT NULL COMMENT 更新者,update_time datetime DEFAULT NULL COMMENT 更新时间,remark varchar(500) DEFAULT NULL COMMENT 备注,PRIMARY KEY (id) ) ENGINEInnoDB AUTO_INCREMENT3 DEFAULT CHARSETutf8mb4 COMMENT角色信息表;CREATE TABLE sys_role_menu (role_id bigint(200) NOT NULL AUTO_INCREMENT COMMENT 角色ID,menu_id bigint(200) NOT NULL DEFAULT 0 COMMENT 菜单ID,PRIMARY KEY (role_id,menu_id) ) ENGINEInnoDB AUTO_INCREMENT2 DEFAULT CHARSETutf8mb4 COMMENT角色和菜单关联表;CREATE TABLE sys_user_role (user_id bigint(200) NOT NULL AUTO_INCREMENT COMMENT 用户ID,role_id bigint(200) NOT NULL DEFAULT 0 COMMENT 角色ID,PRIMARY KEY (user_id,role_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户和角色关联表; 导入菜单实体类 /*** 菜单表(Menu)实体类**/ TableName(valuesys_menu) Data AllArgsConstructor NoArgsConstructor JsonInclude(JsonInclude.Include.NON_NULL) public class Menu implements Serializable {private static final long serialVersionUID -54979041104113736L;TableIdprivate Long id;/*** 菜单名*/private String menuName;/*** 路由地址*/private String path;/*** 组件路径*/private String component;/*** 菜单状态0显示 1隐藏*/private String visible;/*** 菜单状态0正常 1停用*/private String status;/*** 权限标识*/private String perms;/*** 菜单图标*/private String icon;private Long createBy;private Date createTime;private Long updateBy;private Date updateTime;/*** 是否删除0未删除 1已删除*/private Integer delFlag;/*** 备注*/private String remark; } 因为需要查询菜单表所以需要定义菜单表的mapper接口分别增删查改。 Mapper Repository public interface MenuMapper extends BaseMapperMenu {}之后需要使用mapper的xml来实现多表查询 在resource下建包 在mapper接口使用快捷方式altenter生成选择mapper文件夹的位置创建xml映射文件 在application.yml中配置mapperXML文件的位置 # 配置mapperXML文件的位置 mybatis-plus:mapper-locations: classpath*:/mapper/**/*.xml 4.3.2 代码实现 我们只需要根据用户id通过多表联查去查询到其所对应的权限信息即可。 这里就需要使用mapper的xml来实现多表查询 在menu的mapper里面定义根据用户id去查询权限的方法 Mapper Repository public interface MenuMapper extends BaseMapperMenu {ListString selectPermsByUserId(Long userid);}在xml文件里面编写查询权限的sql ?xml version1.0 encodingUTF-8 ? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespacecom.hhy.springboot_demo.mapper.MenuMapperselect idselectPermsByUserId resultTypejava.lang.StringselectDISTINCT m.permsFROMsys_user_role urLEFT JOIN sys_role r ON ur.role_id r.idLEFT JOIN sys_role_menu rm ON ur.role_id rm.role_idLEFT JOIN sys_menu m ON m.id rm.menu_idWHEREuser_id #{userId}AND r.status 0//保证角色是正常状态才查询AND m.status 0//保证菜单是正常状态才查询/select/mapper UserDetailsServiceImpl的登录认证方法中去调用该mapper的方法查询权限信息封装到LoginUser对象中即可也就是替换之前写死的List集合。 对方法权限根据数据库设置的权限改动然后进行测试 数据库表对应说明 接口方法权限设置 4.3.3 测试 测试用户libai 该用户对应拥有权限测试1和权限测试2的访问权限 成功访问满足预期效果、 测试用户test 该用户对应只拥有权限测试2的访问权限 成功访问满足预期结果 5、自定义访问失败处理 我们希望在认证失败或者是授权失败的情况下也能和我们的接口一样返回相同结构的json这样可以让前端对响应进行统一的处理。要实现这个功能我们需要知道SpringSecurity的异常处理机制。 在SpringSecurity中如果我们在认证或者授权的过程中出现了异常会被ExceptionTranslationFilter捕获到。在ExceptionTranslationFilter中会去判断是认证失败还是授权失败出现的异常。如果是认证过程中出现的异常会被封装成AuthenticationException然后调用AuthenticationEntryPoint对象的方法进行异常处理。如果是授权过程中出现的异常会被封装成AccessDeniedException然后调用AccessDeniedHandler对象的方法进行异常处理。所以如果我们需要自定义异常处理我们只需要自定义AuthenticationEntryPoint和AccessDeniedHandler然后配置给SpringSecurity即可。 5.1、自定义实现类 AuthenticationEntryPointImpl /*** 处理认证异常*/ Component public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {Overridepublic void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {RObject result R.AuthenticationEntryPointImpl(用户名认证失败请重新登录, HttpStatus.UNAUTHORIZED.value());String json JSON.toJSONString(result);//处理移除WebUtils.renderString(response,json);} }AccessDeniedHandlerImpl /*** Desc : 授权的异常处理*/ Component public class AccessDeniedHandlerImpl implements AccessDeniedHandler {Overridepublic void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {RObject result R.AuthenticationEntryPointImpl(您的权限不足,HttpStatus.FORBIDDEN.value());String json JSON.toJSONString(result);//处理移除WebUtils.renderString(response,json);} } 其中 WebUtils.renderString public class WebUtils {/*** 在 HttpServletResponse 中渲染字符串的方法。* 它将给定的字符串写入 HttpServletResponse 的输出流* 并设置相应的响应状态码、内容类型和字符编码。*/public static String renderString(HttpServletResponse response,String string) {try {response.getWriter().print(string);response.setStatus(200);response.setContentType(application/json);response.setCharacterEncoding(utf-8);} catch (IOException e) {e.printStackTrace();}return null;} }5.2、配置给SpringSecurity Autowired private AuthenticationEntryPoint authenticationEntryPoint;Autowired private AccessDeniedHandler accessDeniedHandler;//配置异常处理器 http.exceptionHandling()//认证失败处理器.authenticationEntryPoint(authenticationEntryPoint).accessDeniedHandler(accessDeniedHandler); 测试------未改造之前会出现的问题 在用户名出错的时候会抛出异常是因为在执行loadUserByUsername方法时查询用户查询不到 但是如果用户名没错密码错了就不会抛出异常 因为在loadUserByUsername方法执行完成是可以找到用户的获取用户的全部信息所以在做密码验证的时候方法内部验证密码的时候报错了。 改造自定义异常处理之后 密码错误这里走的是用户名无误在查密码时下面的委托机制抛出的异常 Authentication authenticate authenticationManager.authenticate(authenticationToken);用户名错误这里走的是在查不到用户的时候自定义的异常 权限不足–改造之后 没改造之前异常信息也是拿不到的 改造之后走授权异常处理器 6、跨域问题 浏览器出于安全的考虑使用 XMLHttpRequest对象发起 HTTP请求时必须遵守同源策略否则就是跨域的HTTP请求默认情况下是被禁止的。 同源策略要求源相同才能正常进行通信即协议、域名、端口号都完全一致。前后端分离项目前端项目和后端项目一般都不是同源的所以肯定会存在跨域请求的问题。 ​ 所以我们就要处理一下让前端能进行跨域请求。 SpringBoot配置运行跨域请求 CorsConfig Configuration public class CorsConfig implements WebMvcConfigurer {Overridepublic void addCorsMappings(CorsRegistry registry) {// 设置允许跨域的路径registry.addMapping(/**)// 设置允许跨域请求的域名.allowedOriginPatterns(*)// 是否允许cookie.allowCredentials(true)// 设置允许的请求方式.allowedMethods(GET, POST, DELETE, PUT)// 设置允许的header属性.allowedHeaders(*)// 跨域允许时间.maxAge(3600);} } 在SecurityConfig的配置类中开启开启SpringSecurity的跨域访问 由于我们的资源都会收到SpringSecurity的保护所以想要跨域访问还要让SpringSecurity运行跨域访问。 //允许跨域http.cors();7、扩展 7.1、自定义权限校验方法 我们也可以定义自己的权限校验方法在PreAuthorize注解中使用我们的方法。 Component(ex) public class QXExpressionRoot {//String authority 这里是后端赋给它的权限//从数据库获取登录用户的权限功能 和authority 进行对比public boolean hasAuthority(String authority){//获取当前用户得权限Authentication authentication SecurityContextHolder.getContext().getAuthentication();LoginUser loginUser (LoginUser) authentication.getPrincipal();ListString permissions loginUser.getPermissions();//判断用户权限集合中是否存在 authorityreturn permissions.contains(authority);} 在SPEL表达式中使用 ex相当于获取容器中bean的名字为ex的对象。然后再调用这个对象的hasAuthority方法 RequestMapping(hello)//自定义的权限功能PreAuthorize(ex.hasAuthority(system:dept:list))public String hello(){return hello;} 7.2、基于配置的权限控制 在配置类中配置 8、 demo源码 SpringBoot整合Spring Security实现权限控制----------gitee 9、特别鸣谢 作者不易撞的网名 作者北莽
http://www.zqtcl.cn/news/419318/

相关文章:

  • 用织梦做手机移动版网站邯郸网站建设品牌加盟
  • 网站做简历模板动漫设计专业就业方向
  • 沧州市东光建设局 网站电商网站目录优化
  • 公司网站建设案例教程wordpress word文档
  • 阿里巴巴网站本土化建设wordpress jquery
  • 用asp怎么做网站wordpress怎么查看主题
  • 用自己的电脑建网站兴义网站建设
  • 保定医疗网站建设公司wordpress 视频管理 主题
  • php做网站半成品网页设计作业怎么交
  • 郑州网站建设培训学校公众号投票怎么制作
  • 韩国设计交流网站网站设计网页配色
  • 线上设计师网站网络科技公司排名
  • 安徽建设厅网站网址品牌营销ppt
  • 用iis做的网站怎么更改端口南京汤山建设银行网站
  • 威海哪有网站建设十大网页制作工具
  • 上海专业网站建设公司合肥网站建站
  • 怎样将自己做的网站给别人看做平台网站一般有php还是js
  • 做企业网站一般要多少钱WordPress数据库搜索
  • wordpress建立好的网站app的开发流程是什么
  • 工作室网站WordPress文章图片采集插件
  • 青岛网站开发学校wordpress页面样板
  • 校级特色专业建设网站公司网站建设需要些什么要求
  • 嵌入式开发软件有哪些上海谷歌seo
  • 国际学校网站如何建设wordpress登入可见
  • 如何做好网站内链网站开发平台开发
  • 安徽省建设厅网站怎么进不去2022年国内重要新闻
  • 河北建设机械协会网站wordpress怎么做两个语言网站
  • 美容网站模版在线动画手机网站模板
  • jsp做的婚恋网站在谷歌上做英文网站
  • 北京教育学会网站建设昆明seo公司网站