day11-项目集成SpringSecurity-今日指数

项目集成SpringSecurity

学习目标

  • 理解自定义认证和授权过滤器流程;
  • 理解项目集成SprignSecurity流程;

第一章 自定义认证授权过滤器

1、SpringSecurity内置认证流程

通过研究SpringSecurity内置基于form表单认证的UsernamePasswordAuthenticationFilter过滤器,我们可以仿照自定义认证过滤器:

在这里插入图片描述

内置认证过滤器的核心流程:

在这里插入图片描述

核心流程梳理如下:

  • 认证过滤器(UsernamePasswordAuthentionFilter)接收form表单提交的账户、密码信息,并封装成UsernamePasswordAuthenticationToken认证凭对象;
  • 认证过滤器调用认证管理器AuthenticationManager进行认证处理;
  • 认证管理器通过调用用户详情服务获取用户详情UserDetails;
  • 认证管理器通过密码匹配器PasswordEncoder进行匹配,如果密码一致,则将用户相关的权限信息一并封装到Authentication认证对象中;
  • 认证过滤器将Authentication认证过滤器放到认证上下文,方便请求从上下文获取认证信息;

2、自定义Security认证过滤器

​ SpringSecurity内置的认证过滤器是基于post请求且为form表单的方式获取认证数据的,那如何接收前端Json异步提交的数据据实现认证操作呢?

​ 显然,我们可仿照UsernamePasswordAuthentionFilter类自定义一个过滤器并实现认证过滤逻辑;

2.1 自定义认证过滤器

​ UsernamePasswordAuthentionFilter过滤器继承了模板认证过滤器AbstractAuthenticationProcessingFilter抽象类,我们也可仿照实现:

package com.itheima.security.filter;import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.core.userdetails.User;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;public class MyUserNamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {public static final String USER_NAME = "username";public static final String PASSWORD = "password";/*** 通过构造器传入指定的登录url地址* @param defaultFilterProcessesUrl 自定义url登录地址*/public MyUserNamePasswordAuthenticationFilter(String defaultFilterProcessesUrl) {super(defaultFilterProcessesUrl);}@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse res) throws AuthenticationException, IOException, ServletException {//判断请求方法必须是post提交,且提交的数据的内容必须是application/json格式的数据if (!request.getMethod().equals("POST") ||! (request.getContentType().equalsIgnoreCase(MediaType.APPLICATION_JSON_VALUE) || request.getContentType().equalsIgnoreCase(MediaType.APPLICATION_JSON_UTF8_VALUE))) {throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());}//获取请求参数//获取reqeust请求对象的发送过来的数据流ServletInputStream in=request.getInputStream();//将数据流中的数据反序列化MapHashMap<String,String> loginInfo=new ObjectMapper().readValue(in, HashMap.class);String username=loginInfo.get(USER_NAME);username=(username!=null)?username:"";String password=loginInfo.get(PASSWORD);password=(password!=null)?password:"";//将用户名和密码信息封装到认证票据对象下UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);//setDetails(request, authRequest);//调用认证管理器认证指定的票据对象return this.getAuthenticationManager().authenticate(authRequest);}/*** 认证过后执行的方法* @param request* @param response* @param chain 过滤器* @param authResult 认证对象* @throws IOException* @throws ServletException*/@Overrideprotected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {User principal = (User)(authResult.getPrincipal());String username=principal.getUsername();Collection<GrantedAuthority> authorities=principal.getAuthorities();//响应数据格式response.setContentType(MediaType.APPLICATION_JSON_VALUE);response.setCharacterEncoding("utf-8");Map<String,String> info=new HashMap<>();info.put("msg","登录成功");info.put("data","");info.put("code","1");//响应response.getWriter().write(new ObjectMapper().writeValueAsString(info));}@Overrideprotected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {//响应数据格式response.setContentType(MediaType.APPLICATION_JSON_VALUE);response.setCharacterEncoding("utf-8");Map<String,String> info=new HashMap<>();info.put("msg","认证失败");info.put("data","");info.put("code","0");//响应response.getWriter().write(new ObjectMapper().writeValueAsString(info));}
}

2.2 定义获取用户详情服务bean

package com.itheima.service;import com.itheima.entity.TbUser;
import com.itheima.mapper.TbUserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
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 java.util.List;/*** @author by itheima* @Date 2022/5/23* @Description*/
@Service("userDetailsService")
public class MyUserDetailServiceImpl implements UserDetailsService {@Autowiredprivate TbUserMapper tbUserMapper;/*** 使用security当用户认证时,会自动将用户的名称注入到该方法中* 然后我们可以自己写逻辑取加载用户的信息,然后组装成UserDetails认证对象* @param userName* @return 用户的基础信息,包含密码和权限集合,security底层会自动比对前端输入的明文密码* @throws UsernameNotFoundException*/@Overridepublic UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {//1.根据用户名称获取用户的账户信息TbUser dbUser=tbUserMapper.findUserInfoByName(userName);//判断该用户是否存在if (dbUser==null) {throw new UsernameNotFoundException("用户名输入错误!");}//2.组装UserDetails对象//获取当前用户对应的权限集合(自动将以逗号间隔的权限字符串封装到权限集合中)List<GrantedAuthority> list = AuthorityUtils.commaSeparatedStringToAuthorityList(dbUser.getRoles());/*参数1:账户参数2:密码参数3:权限集合*/User user = new User(dbUser.getUsername(), dbUser.getPassword(), list);return user;}
}

2.3 定义SecurityConfig类

配置默认认证过滤器,保证自定义的认证过滤器要在默认的认证过滤器之前;

    /*** 配置授权策略* @param http* @throws Exception*/@Overrideprotected void configure(HttpSecurity http) throws Exception {http.csrf().disable();//禁用跨站请求伪造http.authorizeRequests()//对资源进行认证处理.antMatchers("/authentication/form").permitAll()//登录路径无需拦截.anyRequest().authenticated();  //除了上述资源外,其它资源,只有 认证通过后,才能有权访问http//坑-过滤器要添加在默认过滤器之前,否则,登录失效.addFilterBefore(myUserNamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);}/*** 自定义认证过滤器*  隐含:如果认证成功,则在安全上下文中维护认证相关的信息*      如果上下中存在,则默认MyUserNamePasswordAuthenticationFilter*  所以执行顺序的自定义的在前,成功的话默认的不用再执行了* @return* @throws Exception*/@Beanpublic MyUserNamePasswordAuthenticationFilter myUserNamePasswordAuthenticationFilter() throws Exception {//构造认证管理哭喊 ,并设置认证咱径//设置默认登录路径MyUserNamePasswordAuthenticationFilter authenticationFilter = new MyUserNamePasswordAuthenticationFilter("/myLogin");//设置认证过滤器authenticationFilter.setAuthenticationManager(authenticationManagerBean());return authenticationFilter;}

2.4 自定义认证流程测试

在这里插入图片描述

3、 基于JWT实现无状态认证

​ JWT是无状态的,所以在服务器端无需存储和维护认证信息,这样会大大减轻服务器的压力,所以我们可在自定义的认证过滤器认证成功后通过successfulAuthentication方法向前端颁发token认证字符串;

3.1 认证成功响应JWT实现

测试工程导入Jwt工具类,详见:资料\项目集成代码\JwtTokenUtil.java,集成流程如下:

    /*** 认证工程处理方法* @param request* @param response* @param chain* @param authResult* @throws IOException* @throws ServletException*/@Overrideprotected void successfulAuthentication(HttpServletRequest request,HttpServletResponse response,FilterChain chain,Authentication authResult) throws IOException, ServletException {//认证主体信息,此时以填充用户权限信息UserDetails principal = (UserDetails) authResult.getPrincipal();//组装响应前端的信息String username = principal.getUsername();Collection<? extends GrantedAuthority> authorities = principal.getAuthorities();//构建JwtToken 加入权限信息是为了将来访问时,jwt解析获取当前用户对应的权限,做授权的过滤//权限字符串格式:[P5, ROLE_ADMIN]String token = JwtTokenUtil.createToken(username, authorities.toString());HashMap<String, String> info = new HashMap<>();info.put("name",username);info.put("token",token);//设置响应格式response.setContentType(MediaType.APPLICATION_JSON_VALUE);//内容编码格式response.setCharacterEncoding("utf-8");response.getWriter().write(new ObjectMapper().writeValueAsString(info));}

测试获取认证Token

3.2 SpringSecurity基于Jwt实现认证小结

在这里插入图片描述

4、自定义Security授权过滤

​ 上一小结认证成功后向请求方响应了token信息,那么请求方访问其它系统资源时,就需要带着这个token到后台,后台需要一个授权过滤器获取token信息,并解析用户权限信息,将信息封装到UsernamePasswordAuthentionToken对象存入安全上下文,方便请求时安全过滤处理;

4.1 授权流程说明

在这里插入图片描述

4.2 授权实现流程

定义授权过滤器:

package com.itheima.security.config;import com.google.gson.Gson;
import com.itheima.security.utils.JwtTokenUtil;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
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.util.HashMap;/*** @author by itheima* @Date 2022/1/23* @Description 权限认证filter*/
public class AuthenticationFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest request,HttpServletResponse response,FilterChain filterChain) throws ServletException, IOException {//1.获取http请求头中携带的jwt票据字符串(注意:如果用户尚未认证,则jwt票据字符串不存在)String jwtToken = request.getHeader(JwtTokenUtil.TOKEN_HEADER);//2.判断请求中的票据是否存在if (StringUtils.isBlank(jwtToken)) {//如果票据为空,可能用户准备取认证,所以不做拦截,但是此时UsernamePasswordAuthenticationToken对象未生成,那么即使放行本次请求//后续的过滤器链中也会校验认证票据对象filterChain.doFilter(request,response);return;}//3.校验票据Claims claims = JwtTokenUtil.checkJWT(jwtToken);//票据失效if (claims==null) {//票据失效则提示前端票据已经失效,需要重新认证response.setContentType(MediaType.APPLICATION_JSON_VALUE);response.setContentType("utf-8");response.getWriter().write("jwt token failure!!");return;}//4.从合法的票据中获取用户名和权限信息//用户名String username = JwtTokenUtil.getUsername(jwtToken);//权限信息 [P5, ROLE_ADMIN]String roles = JwtTokenUtil.getUserRole(jwtToken);//将数组格式的字符串转化成权限对象集合String comStr = StringUtils.strip(roles, "[]");List<GrantedAuthority> authorityList = AuthorityUtils.commaSeparatedStringToAuthorityList(comStr);//5.组装认证成功的票据对象(认证成功时,密码位置null)UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, null, authorityList);//6.将认证成功的票据对象保存到安全上下文中,方便后续的过滤器直接获取权限信息SecurityContextHolder.getContext().setAuthentication(token);//7.发行过滤器filterChain.doFilter(request,response);}
}

4.3 配置自定义授权过滤器

/*** 给访问的资源配置权限过滤* @param http* @throws Exception*/
@Override
protected void configure(HttpSecurity http) throws Exception {http.csrf().disable();//禁用跨站请求伪造http.authorizeRequests()//对资源进行认证处理.antMatchers("/myLogin").permitAll()//登录路径无需拦截.anyRequest().authenticated();  //除了上述资源外,其它资源,只有 认证通过后,才能有权访问//添加自定义的认证过滤器:UsernamePasswordAuthenticationFilter是默认的登录认证过滤器,而我们重写了该过滤器,所以访问时,应该先走我们//自定义的过滤器http.addFilterBefore(myUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);//配置授权过滤器,过滤一切资源http.addFilterBefore( authenticationFilter(),MyUsernamePasswordAuthenticationFilter.class);
}  /*** 自定义授权过滤器* 过滤一切被访问的资源* @return*/
@Bean
public AuthenticationFilter authenticationFilter(){AuthenticationFilter filter = new AuthenticationFilter();return filter;
}

访问测试:

​ 略

5、自定义权限拒绝处理

​ 上一小结当用户未认证访问资源或者认证成功后访问没有权限的资源时,响应给前端的信息不友好,我们可通过自定义权限访问拒绝的处理器来友好提醒用户;

5.1 自定义认证用户权限拒绝处理器

通过实现MyAccessDeniedHandler接口实现:

package com.itheima.security.handler;import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.MediaType;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;public class MyAccessDeniedHandler implements AccessDeniedHandler {@Overridepublic void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {//响应数据格式response.setContentType(MediaType.APPLICATION_JSON_VALUE);response.setCharacterEncoding("utf-8");Map<String,String> info=new HashMap<>();info.put("msg","用户无权访问");info.put("data","");info.put("code","0");//响应response.getWriter().write(new ObjectMapper().writeValueAsString(info));}
}

在SecurityConfig中配置

    @Overrideprotected void configure(HttpSecurity http) throws Exception {http.formLogin() //开启默认form表单登录方式.and().logout()//登出用默认的路径登出 /logout.permitAll()//允许所有的用户访问登录或者登出的路径.and().csrf().disable()//启用CSRF,防止CSRF攻击.authorizeRequests()//授权方法,该方法后有若干子方法进行不同的授权规则处理//允许所有账户都可访问(不登录即可访问),同时可指定多个路径
//                .antMatchers("/register").permitAll()
//                .antMatchers("/hello").hasAuthority("P5") //具有P5权限才可以访问
//                .antMatchers("/say").hasRole("ADMIN") //具有ROLE_ADMIN 角色才可以访问.anyRequest().authenticated(); //除了上边配置的请求资源,其它资源都必须授权才能访问//将自定义的过滤器加入security过滤链,且在默认证过滤器之前执行http.addFilterBefore(myUserNamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);//配置授权过滤器,过滤一切资源http.addFilterBefore(authenticationFilter(),UsernamePasswordAuthenticationFilter.class);http.exceptionHandling().accessDeniedHandler(new MyAccessDeniedHandler());}

效果:

在这里插入图片描述

5.2 自定义匿名用户拒绝处理器

同样通过实现AuthenticationEntryPoint接口实现:

    /*** 自定义登录认证策略配置授权策略 -1* @param http* @throws Exception*/@Overrideprotected void configure(HttpSecurity http) throws Exception {//......http.exceptionHandling().accessDeniedHandler(new AccessDeniedHandler() {@Overridepublic void handle(HttpServletRequest request,HttpServletResponse response,AccessDeniedException e) throws IOException, ServletException {//认证用户访问资源时权限拒绝处理策略response.getWriter().write("no permission......reject....");}}).authenticationEntryPoint(new AuthenticationEntryPoint(){@Overridepublic void commence(HttpServletRequest request,HttpServletResponse response,AuthenticationException authException) throws IOException, ServletException {response.setContentType(MediaType.APPLICATION_JSON_VALUE);response.setCharacterEncoding("UTF-8");response.getWriter().write("匿名用户无权访问");});}
}                                         

6、自定义认证授权整体流程小结

在这里插入图片描述

在这里插入图片描述

第二章 项目集成SpringSecurity

​ 在第一章我们是基于SpringSecurity、JWT技术实现前后端无状态化认证授权,而我们当前的项目是前后端分离的架构,同样也可借助Security框架和Jwt实现前后端的无状态认证授权操作;

1、项目自定义认证过滤器

1.1 依赖导入

在stock_backend工程导入SpringSecurity启动依赖:

<!--引入security-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>

1.2 自定义认证过虑器

当前项目中认证登录信息的合法性,除了用户名、密码外,还需要校验验证码,所以认证过滤器需要注入redis模板对象:

package com.itheima.stock.security.filter;import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Strings;
import com.itheima.stock.constant.StockConstant;
import com.itheima.stock.security.detail.LoginUserDetail;
import com.itheima.stock.utils.JwtTokenUtil;
import com.itheima.stock.vo.req.LoginReqVo;
import com.itheima.stock.vo.resp.LoginRespVoExt;
import com.itheima.stock.vo.resp.R;
import com.itheima.stock.vo.resp.ResponseCode;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;/*** @author by itheima* @Date 2022/7/14* @Description*/
public class JwtLoginAuthenticationFilter extends AbstractAuthenticationProcessingFilter {private RedisTemplate redisTemplate;/*** 通过setter方法注解redis模板对象* @param redisTemplate*/public void setRedisTemplate(RedisTemplate redisTemplate) {this.redisTemplate = redisTemplate;}/*** 通过构造器传入自定义的登录地址* @param loginUrl*/public JwtLoginAuthenticationFilter(String loginUrl) {super(loginUrl);}/*** 用户认证处理的方法* @param request* @param response* @return* @throws AuthenticationException* @throws IOException* @throws ServletException* 我们约定请求方式必须是post方式,且请求的数据时json格式*              约定请求是账户key:username  密码:password*/@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {//判断请求方法必须是post提交,且提交的数据的内容必须是application/json格式的数据if (!request.getMethod().equals("POST") ||! (request.getContentType().equalsIgnoreCase(MediaType.APPLICATION_JSON_VALUE) || request.getContentType().equalsIgnoreCase(MediaType.APPLICATION_JSON_UTF8_VALUE))) {throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());}//获取请求参数//获取reqeust请求对象的发送过来的数据流ServletInputStream in = request.getInputStream();//将数据流中的数据反序列化成MapLoginReqVo vo = new ObjectMapper().readValue(in, LoginReqVo.class);response.setContentType(MediaType.APPLICATION_JSON_VALUE);response.setCharacterEncoding("utf-8");//1.判断参数是否合法if (vo==null || StringUtils.isBlank(vo.getUsername())|| StringUtils.isBlank(vo.getPassword())|| StringUtils.isBlank(vo.getSessionId()) || StringUtils.isBlank(vo.getCode())) {R<Object> resp = R.error(ResponseCode.USERNAME_OR_PASSWORD_ERROR.getMessage());response.getWriter().write(new ObjectMapper().writeValueAsString(resp));return null;}//从程序执行的效率看,先进行校验码的校验,成本较低String rCheckCode =(String) redisTemplate.opsForValue().get(StockConstant.CHECK_PREFIX + vo.getSessionId());if (rCheckCode==null || ! rCheckCode.equalsIgnoreCase(vo.getCode())) {//响应验证码输入错误R<Object> resp = R.error(ResponseCode.CHECK_CODE_ERROR.getMessage());response.getWriter().write(new ObjectMapper().writeValueAsString(resp));return null;}String username = vo.getUsername();//username = (username != null) ? username : "";username = username.trim();String password = vo.getPassword();//password = (password != null) ? password : "";//将用户名和密码信息封装到认证票据对象下UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);// Allow subclasses to set the "details" property//setDetails(request, authRequest);//调用认证管理器认证指定的票据对象return this.getAuthenticationManager().authenticate(authRequest);}
}

2、自定义用户详情服务

​ 上一小结,我们完成了认证过滤器的开发,认证过程中认证管理器AutenticationManager底层会调用用户详情服务对象获取用户详情信息,所以接下来我们需要实现用户详情服务;

权限表注意事项:

在这里插入图片描述

2.1 自定义UserDetail认证详情信息类

package com.itheima.stock.security.detail;import com.itheima.stock.vo.resp.PermissionRespNodeVo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;import java.util.Collection;
import java.util.List;/*** @author by itheima* @Date 2022/7/14* @Description 自定义用户认证详情类*/
@Data//setter getter toString
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class LoginUserDetail implements UserDetails {/*** 用户名称*/private String username;
//    @Override
//    public String getUsername() {
//        return null;
//    }/*** 密码*/private String password;
//    @Override
//    public String getPassword() {
//        return null;
//    }/*** 权限信息*/private List<GrantedAuthority> authorities;
//    @Override
//    public Collection<? extends GrantedAuthority> getAuthorities() {
//        return null;
//    }/*** 账户是否过期*/private boolean isAccountNonExpired=true;
//    @Override
//    public boolean isAccountNonExpired() {
//        return false;
//    }/*** 账户是否被锁定*  true:没有被锁定*/private boolean isAccountNonLocked=true;
//    @Override
//    public boolean isAccountNonLocked() {
//        return false;
//    }/*** 密码是否过期*  true:没有过期*/private boolean isCredentialsNonExpired=true;
//    @Override
//    public boolean isCredentialsNonExpired() {
//        return false;
//    }/*** 账户是否禁用*  true:没有禁用*/private boolean isEnabled=true;
//    @Override
//    public boolean isEnabled() {
//        return false;
//    }/*** 用户ID*/private String id;/*** 电话*/private String phone;/*** 昵称*/private String nickName;/*** 真实姓名*/private String realName;/*** 性别(1.男 2.女)*/private Integer sex;/*** 账户状态(1.正常 2.锁定 )*/private Integer status;/*** 邮箱*/private String email;/*** 权限树,不包含按钮相关权限信息*/private List<PermissionRespNodeVo> menus;/*** 按钮权限树*/private List<String> permissions;
}

2.2 自定义UserDetailsService实现

package com.itheima.stock.security.service;import com.google.common.base.Strings;
import com.itheima.stock.mapper.SysPermissionMapper;
import com.itheima.stock.mapper.SysRoleMapper;
import com.itheima.stock.mapper.SysUserMapperExt;
import com.itheima.stock.pojo.entity.SysPermission;
import com.itheima.stock.pojo.entity.SysRole;
import com.itheima.stock.pojo.entity.SysUser;
import com.itheima.stock.security.detail.LoginUserDetail;
import com.itheima.stock.service.PermissionService;
import com.itheima.stock.vo.resp.PermissionRespNodeVo;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
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.Component;import java.util.List;
import java.util.stream.Collectors;/*** @author by itheima* @Date 2022/7/13* @Description 定义获取用户合法详情信息的服务*/
@Component
public class LoginUserDetailService implements UserDetailsService {@Autowiredprivate SysUserMapperExt sysUserMapperExt;@Autowiredprivate SysPermissionMapper sysPermissionMapper;@Autowiredprivate SysRoleMapper sysRoleMapper;@Autowiredprivate PermissionService permissionService;/*** 当用户登录认证是,底层会自动调用MyUserDetailService#loadUserByUsername()把登录的账户名称传入* 根据用户名称获取用户的详情信息:用户名 加密密码 权限集合,还包含前端需要的侧边栏树 、前端需要的按钮权限标识的集合等* @param loginName* @return* @throws UsernameNotFoundException*/@Overridepublic UserDetails loadUserByUsername(String loginName) throws UsernameNotFoundException {//2.根据用户名查询用户信息SysUser dbUser= sysUserMapperExt.findUserByUserName(username);//3.判断查询的用户信息if (dbUser==null) {throw new UsernameNotFoundException("用户不存在");}//4.2 成功则返回用户的正常信息//获取指定用户的权限集合 添加获取侧边栏数据和按钮权限的结合信息List<SysPermission> permissions = permissionService.getPermissionByUserId(dbUser.getId());//前端需要的获取树状权限菜单数据List<PermissionRespNodeVo> tree = permissionService.getTree(permissions, 0l, true);//前端需要的获取菜单按钮集合List<String> authBtnPerms = permissions.stream().filter(per -> !Strings.isNullOrEmpty(per.getCode()) && per.getType() == 3).map(per -> per.getCode()).collect(Collectors.toList());//5.组装后端需要的权限标识//5.1 获取用户拥有的角色List<SysRole> roles = sysRoleMapper.getRoleByUserId(dbUser.getId());//5.2 将用户的权限标识和角色标识维护到权限集合中List<String> perms=new ArrayList<>();permissions.stream().forEach(per->{if (StringUtils.isNotBlank(per.getPerms())) {perms.add(per.getPerms());}});roles.stream().forEach(role->{perms.add("ROLE_"+role.getName());});String[] permStr=perms.toArray(new String[perms.size()]);//5.3 将用户权限标识转化成权限对象集合List<GrantedAuthority> authorityList = AuthorityUtils.createAuthorityList(permStr);//6.封装用户详情信息实体对象LoginUserDetail loginUserDetail = new LoginUserDetail();//将用户的id nickname等相同属性信息复制到详情对象中BeanUtils.copyProperties(dbUser,loginUserDetail);loginUserDetail.setMenus(tree);loginUserDetail.setAuthorities(authorityList);loginUserDetail.setPermissions(authBtnPerms);return loginUserDetail;}
}

2.3 完善相关mapper

A.定义根据用户名查询用户信息的接口方法

在SysUserMapper定义方法:

    /*** 根据用户名查询用户信息* @param username* @return*/SysUser findUserByUserName(@Param("username") String username);

绑定xml:

    <select id="findUserByUserName" resultMap="BaseResultMap">select <include refid="Base_Column_List"/> from sys_user where username=#{username}</select>

B.定义根据用户id查询角色信息的接口方法

在SysRoleMapper定义方法:

    /*** 根据用户id查询角色信息* @param userId* @return*/List<SysRole> getRoleByUserId(@Param("userId") Long userId);

绑定xml:

<select id="getRoleByUserId" resultMap="BaseResultMap">SELECTr.*FROMsys_user_role AS ur,sys_role AS rWHEREur.role_id = r.idAND ur.user_id = #{userId}
</select>

C.定义根据用户id查询权限信息的接口方法

在SysPermissionMapper定义方法:

    /*** 根据用户id查询用户信息* @param userId* @return*/List<SysPermission> getPermissionByUserId(@Param("userId") Long userId);

绑定xml:

    <select id="getPermissionByUserId" resultMap="BaseResultMap">SELECTdistinct  p.*FROMsys_role_permission AS rp,sys_user_role AS ur,sys_permission AS pWHEREur.role_id = rp.role_idAND rp.permission_id = p.idAND ur.user_id = #{userId}</select>

3、认证成功后响应Token实现

在认证成功方法successfulAuthentication中,基于jwt将用户信息和相关的权限信息通过jwt加密响应给前端请求:

public class JwtLoginAuthenticationFilter extends AbstractAuthenticationProcessingFilter {//省略N行代码....../*** 用户认证成功后回调的方法* 认证成功后,响应前端token信息* @param request* @param response* @param chain security的过滤器链* @param authResult* @throws IOException* @throws ServletException*/@Overrideprotected void successfulAuthentication(HttpServletRequest request,HttpServletResponse response,FilterChain chain,Authentication authResult) throws IOException, ServletException {//获取用户的详情信息LoginUserDetail userDetail = (LoginUserDetail) authResult.getPrincipal();//组装LoginRespVoExt//获取用户名称String username = userDetail.getUsername();//获取权限集合对象List<GrantedAuthority> authorities = userDetail.getAuthorities();String auStrList = authorities.toString();//复制userDetail属性值到LoginRespVoExt对象即可LoginRespVoExt resp = new LoginRespVoExt();BeanUtils.copyProperties(userDetail,resp);//生成token字符串:将用户名称和权限信息价格生成token字符串String tokenStr = JwtTokenUtil.createToken(username, auStrList);resp.setAccessToken(tokenStr);//封装统一响应结果R<Object> r = R.ok(resp);String respStr = new ObjectMapper().writeValueAsString(r);response.setContentType(MediaType.APPLICATION_JSON_VALUE);response.setCharacterEncoding("UTF-8");response.getWriter().write(respStr);}/*** 认证失败后,回调的方法* @param request* @param response* @param failed* @throws IOException* @throws ServletException*/@Overrideprotected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {R<Object> r = R.error(ResponseCode.SYSTEM_PASSWORD_ERROR);String respStr = new ObjectMapper().writeValueAsString(r);response.setContentType(MediaType.APPLICATION_JSON_VALUE);response.setCharacterEncoding("UTF-8");response.getWriter().write(respStr);}

4、定义Security配置类

过程同第一章节大致一样,需要注意swagger访问、knif4j、验证码等资源设置pertmall访问权限;

package com.itheima.stock.security.config;import com.itheima.stock.security.filter.JwtAuthorizationFilter;
import com.itheima.stock.security.filter.JwtLoginAuthenticationFilter;
import com.itheima.stock.security.handler.StockAccessDenyHandler;
import com.itheima.stock.security.handler.StockAuthenticationEntryPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
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.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;/*** @author by itheima* @Date 2022/7/14* @Description*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig  extends WebSecurityConfigurerAdapter {@Autowiredprivate RedisTemplate redisTemplate;/*** 定义公共的无需被拦截的资源* @return*/private String[] getPubPath(){//公共访问资源String[] urls = {"/**/*.css","/**/*.js","/favicon.ico","/doc.html","/druid/**","/webjars/**","/v2/api-docs","/api/captcha","/swagger/**","/swagger-resources/**","/swagger-ui.html"};return urls;}/*** 配置过滤规则* @param http* @throws Exception*/@Overrideprotected void configure(HttpSecurity http) throws Exception {//登出功能http.logout().logoutUrl("/api/logout").invalidateHttpSession(true);//开启允许iframe 嵌套。security默认禁用ifram跨域与缓存http.headers().frameOptions().disable().cacheControl().disable();//session禁用http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);http.csrf().disable();//禁用跨站请求伪造http.authorizeRequests()//对资源进行认证处理.antMatchers(getPubPath()).permitAll()//公共资源都允许访问.anyRequest().authenticated();  //除了上述资源外,其它资源,只有 认证通过后,才能有权访问//自定义的过滤器http.addFilterBefore(jwtLoginAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);}/*** 自定义认证过滤器bean* @return* @throws Exception*/@Beanpublic JwtLoginAuthenticationFilter jwtLoginAuthenticationFilter() throws Exception {JwtLoginAuthenticationFilter filter = new JwtLoginAuthenticationFilter("/api/login");filter.setAuthenticationManager(authenticationManagerBean());filter.setRedisTemplate(redisTemplate);return filter;}/*** 定义密码加密器,实现对明文密码的加密和匹配操作* @return*/@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}
}

注意:认证过滤器配置完毕后,需要注释掉原来的登录入口;

断点调试认证流程:

​ 略;

5、自定义授权过滤器

授权的逻辑与第一章相似,需要实现OncePerRequestFilter过滤器;

5.1 定义授权顾虑器

package com.itheima.stock.security.filter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.itheima.stock.constant.StockConstant;
import com.itheima.stock.utils.JwtTokenUtil;
import com.itheima.stock.vo.resp.R;
import com.itheima.stock.vo.resp.ResponseCode;
import io.jsonwebtoken.Claims;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
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.util.List;/*** @author by itheima* @Date 2022/7/14* @Description 定义授权过滤器,核心作用:*      1.过滤请求,获取请求头中的token字符串*      2.解析token字符串,并获取token中信息:username role*      3.将用户名和权限信息封装到UsernamePassowrdAuthentionToken票据对象下*      4.将票据对象放入安全上下文,方便校验权限时,随时获取*/
public class JwtAuthorizationFilter extends OncePerRequestFilter {/*** 访问过滤的方法* @param request* @param response* @param filterChain 过滤器链* @throws ServletException* @throws IOException*/@Overrideprotected void doFilterInternal(HttpServletRequest request,HttpServletResponse response,FilterChain filterChain) throws ServletException, IOException {//1.从request对象下获取token数据,约定key:AuthorizationString tokenStr = request.getHeader(StockConstant.TOKEN_HEADER);//判断token字符串是否存在if (tokenStr==null) {//如果票据为null,可能用户还没有认证,正准备去认证,所以放行请求//放行后,会不会访问当受保护的资源呢?不会,因为没有生成UsernamePassowrdAuthentionTokenfilterChain.doFilter(request,response);return;}//2.解析tokenStr,获取用户详情信息Claims claims = JwtTokenUtil.checkJWT(tokenStr);//token字符串失效的情况if (claims==null) {//说明 票据解析出现异常,票据就失效了R<Object> r = R.error(ResponseCode.TOKEN_NO_AVAIL);String respStr = new ObjectMapper().writeValueAsString(r);response.setContentType(MediaType.APPLICATION_JSON_VALUE);response.setCharacterEncoding("UTF-8");response.getWriter().write(respStr);return;}//获取用户名和权限信息String userName = JwtTokenUtil.getUsername(tokenStr);//生成token时,权限字符串的格式是:[P8,ROLE_ADMIN]String perms = JwtTokenUtil.getUserRole(tokenStr);//生成权限集合对象//P8,ROLE_ADMINString strip = StringUtils.strip(perms, "[]");List<GrantedAuthority> authorityList = AuthorityUtils.commaSeparatedStringToAuthorityList(strip);//将用户名和权限信息封装到token对象下UsernamePasswordAuthenticationToken token=new UsernamePasswordAuthenticationToken(userName,null,authorityList);//将token对象存入安全上限文,这样,线程无论走到哪里,都可以获取token对象,验证当前用户访问对应资源是否被授权SecurityContextHolder.getContext().setAuthentication(token);//资源发行filterChain.doFilter(request,response);}
}

5.2 配置授权过滤器

在SecurityConfig类中配置授权过滤器:

    /*** 配置过滤规则* @param http* @throws Exception*/@Overrideprotected void configure(HttpSecurity http) throws Exception {//......省略//配置授权过滤器,过滤一切资源http.addFilterBefore(jwtAuthorizationFilter(),JwtLoginAuthenticationFilter.class);}/*** 自定义授权过滤器* @return*/@Beanpublic JwtAuthorizationFilter jwtAuthorizationFilter(){return new JwtAuthorizationFilter();}

授权访问测试;

​ 略

6、定义权限拒绝处理器

  • 定义用户认证成功无权限访问处理器
package com.itheima.stock.security.handler;import com.fasterxml.jackson.databind.ObjectMapper;
import com.itheima.stock.vo.resp.R;
import com.itheima.stock.vo.resp.ResponseCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;/*** @author by itheima* @Date 2022/7/14* @Description 定义没有权限,访问拒绝的处理器* 用户认证成功,但是没有访问的权限,则会除非拒绝处理器* 如果是匿名用户访问被拒绝则使用匿名拒绝的处理器*/
@Slf4j
public class StockAccessDenyHandler implements AccessDeniedHandler {@Overridepublic void handle(HttpServletRequest request,HttpServletResponse response,AccessDeniedException ex) throws IOException, ServletException {log.info("访问拒绝,异常信息:{}",ex.getMessage());//说明 票据解析出现异常,票据就失效了R<Object> r = R.error(ResponseCode.NOT_PERMISSION);String respStr = new ObjectMapper().writeValueAsString(r);response.setContentType(MediaType.APPLICATION_JSON_VALUE);response.setCharacterEncoding("UTF-8");response.getWriter().write(respStr);}
}
  • 匿名用户(未认证用户)访问拒绝处理器
package com.itheima.stock.security.handler;import com.fasterxml.jackson.databind.ObjectMapper;
import com.itheima.stock.vo.resp.R;
import com.itheima.stock.vo.resp.ResponseCode;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;/*** @author by itheima* @Date 2022/7/14* @Description*  未认证的用户访问被拒绝的处理器*/
public class StockAuthenticationEntryPoint implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest request,HttpServletResponse response,AuthenticationException authException) throws IOException, ServletException {//说明 票据解析出现异常,票据就失效了R<Object> r = R.error(ResponseCode.NOT_PERMISSION);String respStr = new ObjectMapper().writeValueAsString(r);response.setContentType(MediaType.APPLICATION_JSON_VALUE);response.setCharacterEncoding("UTF-8");response.getWriter().write(respStr);}
}
  • 配置类配置处理器

在SecurityConfig类配置处理器:

    /*** 配置过滤规则* @param http* @throws Exception*/@Overrideprotected void configure(HttpSecurity http) throws Exception {//省略......//配置权限访问拒绝处理器http.exceptionHandling().accessDeniedHandler(new StockAccessDenyHandler()).authenticationEntryPoint(new StockAuthenticationEntryPoint());}

测试拒绝处理器:

​ 略

7、配置资源访问权限注解

为接口添加权限注解,比如:

@RestController
@RequestMapping("/api")
public class LogController {@Autowiredprivate LogService logService;/*** 日志信息综合查询* @param vo* @return*/@PreAuthorize("hasAuthority('sys:log:list')")@PostMapping("/logs")public R<PageResult> logPageQuery(@RequestBody LogPageReqVo vo){return logService.logPageQuery(vo);}/*** 批量删除日志信息功能* @param logIds* @return*/@DeleteMapping("/log")@PreAuthorize("hasAuthority('sys:log:delete')")public R<String> deleteBatch(@RequestBody List<Long> logIds){return this.logService.deleteBatch(logIds);}}

使用test和admin用户分别访问测试:

略;

8、前端动态路由调整

当前前端的路由资源都是配置死的,而我们需要根据不同的用户加载不同路由资源,所以前端需要做如下调整:

前端请求时携带token逻辑如下:

在这里插入图片描述

登录逻辑中注释掉默认的参数配置:

在这里插入图片描述

注释掉默认的路由组件,自动加载来自后台的动态路由参数;

在这里插入图片描述

访问测试:用户:test 密码:123456登录后效果:

在这里插入图片描述

用户:admin 密码:123456登录后效果:

在这里插入图片描述

用户登录信息保存在前端SessionStorage下:

在这里插入图片描述

onHandling().accessDeniedHandler(new StockAccessDenyHandler())
.authenticationEntryPoint(new StockAuthenticationEntryPoint());
}


测试拒绝处理器:​	略## 7、配置资源访问权限注解为接口添加权限注解,比如:~~~java
@RestController
@RequestMapping("/api")
public class LogController {@Autowiredprivate LogService logService;/*** 日志信息综合查询* @param vo* @return*/@PreAuthorize("hasAuthority('sys:log:list')")@PostMapping("/logs")public R<PageResult> logPageQuery(@RequestBody LogPageReqVo vo){return logService.logPageQuery(vo);}/*** 批量删除日志信息功能* @param logIds* @return*/@DeleteMapping("/log")@PreAuthorize("hasAuthority('sys:log:delete')")public R<String> deleteBatch(@RequestBody List<Long> logIds){return this.logService.deleteBatch(logIds);}}

使用test和admin用户分别访问测试:

略;

8、前端动态路由调整

当前前端的路由资源都是配置死的,而我们需要根据不同的用户加载不同路由资源,所以前端需要做如下调整:

前端请求时携带token逻辑如下:

在这里插入图片描述

登录逻辑中注释掉默认的参数配置:
在这里插入图片描述

注释掉默认的路由组件,自动加载来自后台的动态路由参数;

在这里插入图片描述

访问测试:用户:test 密码:123456登录后效果:

在这里插入图片描述

用户:admin 密码:123456登录后效果:

在这里插入图片描述

用户登录信息保存在前端SessionStorage下:

在这里插入图片描述

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

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

相关文章

【工程院院士加盟】第四届计算机通信与人工智能国际会议

CCAI 2024 | Xian, Chinahttp://ccai.net/ - IEEE出版&#xff0c;EI核心和Scopus检索 - 工程院院士&#xff0c;IEEE Fellow等学术大咖主题演讲 - 会议时间-地点&#xff1a;2024年5月24-26日&#xff0c;中国西安 会议简介 Brief Introduction 作为人工智能的重要传播技术…

Linux环境下基本指令

今天我们一起来认识一下Linux环境下一些基本的指令&#xff0c;这些指令是我们学习Linux的基础&#xff0c;只有掌握了这些指令&#xff0c;我们才能在Linux环境下进一步学习知识&#xff0c;话不多说&#xff0c;我们开始&#xff08;以下演示操作是在云服务器的环境下&#x…

基于SSM的车位租赁系统(有报告)。Javaee项目。ssm项目。

演示视频&#xff1a; 基于SSM的车位租赁系统&#xff08;有报告&#xff09;。Javaee项目。ssm项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通过Spring Spri…

【数据分析之Numpy基础004】数学运算大揭秘:轻松玩转ndarray的强大实力

ndarray的数学运算是一项非常重要的操作&#xff0c;包括不同对象之间的四则运算&#xff0c;三角函数变换、求和、求平均等操作 1、四则运算 如果参与运算的两个对象都是ndarray&#xff0c;并且形状相同&#xff0c;那么就可以进行对位之间的四则&#xff08; - * / &#x…

前后端分离vue.js+nodejs学生考勤请假系统 _fbo36

此系统设计主要采用的是nodejs语言来进行开发&#xff0c;采用vue框架技术&#xff0c;框架分为三层&#xff0c;分别是控制层Controller&#xff0c;业务处理层Service&#xff0c;持久层dao&#xff0c;能够采用多层次管理开发&#xff0c;对于各个模块设计制作有一定的安全性…

UI设计中,2D、2.5D、3D、4D该如何辨别?教会你

hello&#xff0c;我是大千UI工场&#xff0c;从事UI设计8年之久&#xff0c;在日常工作中经常听到一些概念&#xff0c;现在将这些概念图文并茂的呈现给您&#xff0c;欢迎点赞评论&#xff0c;如有设计需求&#xff0c;可以私信我们。 在UI设计中&#xff0c;2D、2.5D、3D和4…

【Pytorch深度学习开发实践学习】B站刘二大人课程笔记整理lecture09 Softmax多分类

代码&#xff1a; import torch from torchvision import datasets, transforms from torch.utils.data import DataLoader import torch.nn as nn import torch.nn.functional as Fbatch_size 64 transform transforms.Compose([transforms.ToTensor(), transforms.Normali…

python:读 Freeplane.mm文件,使用 xml.etree 生成测试案例.csv文件

Freeplane 是一款基于 Java 的开源软件&#xff0c;继承 Freemind 的思维导图工具软件&#xff0c;它扩展了知识管理功能&#xff0c;在 Freemind 上增加了一些额外的功能&#xff0c;比如数学公式、节点属性面板等。 强大的节点功能&#xff0c;不仅仅节点的种类很多&#xf…

不用加减乘除做加法

1.题目&#xff1a; 写一个函数&#xff0c;求两个整数之和&#xff0c;要求在函数体内不得使用、-、*、/四则运算符号。 数据范围&#xff1a;两个数都满足 −10≤&#xfffd;≤1000−10≤n≤1000 进阶&#xff1a;空间复杂度 &#xfffd;(1)O(1)&#xff0c;时间复杂度 &am…

web安全学习笔记【13】——信息打点(3)

信息打点-JS架构&框架识别&泄漏提取&API接口枚举&FUZZ爬虫&插件项目[1] #知识点&#xff1a; 1、业务资产-应用类型分类 2、Web单域名获取-接口查询 3、Web子域名获取-解析枚举 4、Web架构资产-平台指纹识别 ------------------------------------ 1、开源…

2.5G/5G/10G高速率网络变压器(网络隔离变压器)产品介绍(1)

Hqst华轩盛(石门盈盛)电子导读&#xff1a;高速率/2.5G 的带POE插件&#xff08;DIP&#xff09;款千兆双口网络变压器2G54801DP特点 一 ﹑2.5G高速率网络变压器&#xff08;网络隔离变压器&#xff09;&#xff1a;2G54801DP外观与尺寸 2G54801DP这颗产品尺寸为&#xff1a;长…

C++ 学习(1)---- 左值 右值和右值引用

这里写目录标题 左值右值左值引用和右值引用右值引用和移动构造函数std::move 移动语义返回值优化移动操作要保证安全 万能引用std::forward 完美转发传入左值传入右值 左值 左值是指可以使用 & 符号获取到内存地址的表达式&#xff0c;一般出现在赋值语句的左边&#xff…

【Python笔记-设计模式】装饰器模式

一、说明 装饰器模式是一种结构型设计模式&#xff0c;旨在动态的给一个对象添加额外的职责。 (一) 解决问题 不改变原有对象结构的情况下&#xff0c;动态地给对象添加新的功能或职责&#xff0c;实现透明地对对象进行功能的扩展。 (二) 使用场景 如果用继承来扩展对象行…

数据结构-二分搜索树(Binary Search Tree)

一,简单了解二分搜索树 树结构: 问题:为什么要创造这种数据结构 1,树结构本身是一种天然的组织结构,就好像我们的文件夹一样,一层一层的. 2,树结构可以更高效的处理问题 二,二分搜索树的基础 1、二叉树 2,二叉树的重要特性 满二叉树 总结: 1. 叶子结点出现在二叉树的最…

HDL FPGA 学习 - Quartus II 工程搭建,ModelSim 仿真,时序分析,IP 核使用,Nios II 软核使用,更多技巧和规范总结

目录 工程搭建、仿真与时钟约束 一点技巧 ModelSim 仿真 Timing Analyzer 时钟信号约束 SignalTap II 使用 In-System Memory Content Editor 使用 记录 QII 的 IP 核使用 记录 Qsys/Nios II 相关 记录 Qsys 的 IP 核使用 封装 Avalon IP 更多小技巧教程文章 更多好…

设计模式篇---观察者模式

文章目录 概念结构实例总结 概念 观察者模式&#xff1a;定义对象之间的一种一对多的依赖关系&#xff0c;使得每当一个对象状态发生改变时&#xff0c;其他相关依赖对象都得到通知并被自动更新。 观察者模式是使用频率较高的一个模式&#xff0c;它建立了对象与对象之间的依赖…

C/C++的内存管理(1)

内存管理 C与C的内存分布C语言中动态内存管理方式回顾C内存管理的方式 C与C的内存分布 我们学习C语言时就知道&#xff0c;储存不同的变量计算机会相应分配不同区块的内存。那为什么要把内存化为不同的区域呢&#xff1f;实质上是为了方便管理 下面我们来看看下面一道例题&…

如何开发自己的npm包并上传到npm官网可以下载

目录 搭建文件结构 开始编写 发布到npm 如何下载我们发布的npm包 搭建文件结构 先创建新文件夹,按照下面的样子布局 .├── README.md //说明文档 ├── index.js //主入口 ├── lib //功能文件 └── tests //测试用例 然后再此根目录下初始化package包 npm init…

MyBatis---初阶

一、MyBatis作用 是一种更简单的操作和读取数据库的工具。 二、MyBatis准备工作 1、引入依赖 2、配置Mybatis(数据库连接信息) 3、定义接口 Mapper注解是MyBatis中用来标识接口为Mapper接口的注解。在MyBatis中&#xff0c;Mapper接口是用来定义SQL映射的接口&#xff0c;通…

招聘APP开发实践:技术选型、架构设计与开发流程

时下&#xff0c;招聘APP成为了企业和求职者之间连接的重要纽带。本文将深入探讨招聘APP的开发实践&#xff0c;重点关注技术选型、架构设计以及开发流程等关键方面&#xff0c;带领读者走进这一充满挑战与机遇的领域。 一、技术选型 在开始招聘APP的开发之前&#xff0c;首…