SpringSecurity实现自定义登录接口

server/2024/10/18 16:54:06/

SpringSecurity实现自定义登录接口

1、配置类 ConfigClazz(SpringSecuriey的)
java">    //首先就是要有一个配置类@Resourceprivate DIYUsernamePasswordAuthenticationFilter diyUsernamePasswordAuthenticationFilter;/*SpringSecurity配置*/@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http.authorizeRequests(authorize -> authorize.requestMatchers("/user/**","/").hasRole("user") //拥有user的角色可访问的接口.requestMatchers("/manager/**").hasRole("manager")//拥有manager的角色可访问的接口.requestMatchers("/login/**").permitAll().anyRequest() .authenticated() // 任何请求都需要授权,重定向到);/*登录页*/http.formLogin(AbstractHttpConfigurer::disable);//禁用默认的登录接口,使用自定义的登录接口/*登出*/http.logout(logout ->{logout.logoutUrl("/goOut").permitAll()//登录退出成功,向前端返回json格式的字符串.logoutSuccessHandler((HttpServletRequest request, HttpServletResponse response, Authentication authentication)->{Map<String, String[]> parameterMap = request.getParameterMap();//进入登录页时,判断是否已经登陆过 TowLogin 参数if(!parameterMap.isEmpty() && parameterMap.get("TowLogin")[0].equals("true")){String json = JSON.toJSONString(Code.NOTowLogin);response.setContentType("application/json;charset=UTF-8");response.getWriter().println(json);} else {String json = JSON.toJSONString(Code.SuccessLogout);response.setContentType("application/json;charset=UTF-8");response.getWriter().println(json);}});});/*向过滤器链中添加自定义的过滤器用自定义的过滤器代替 UsernamePasswordAuthenticationFilter 过滤器*/http.addFilterAfter(diyUsernamePasswordAuthenticationFilter, LogoutFilter.class);/*请求异常处理*/http.exceptionHandling(exception ->{/*用户未登录时,访问限权接口,返回 json 格式的字符串这个配是。把页面跳转交给前端,即:用户未登录时,后端只返回 json 格式的字符串,不会跳转页面-- 未登录时,重定向的 url  .loginPage("/login/getLoginHTML").permitAll(),就不起作用了 --*/exception.authenticationEntryPoint((HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)->{String json = JSON.toJSONString(Code.NoLogin);response.setContentType("application/json;charset=UTF-8");response.getWriter().println(json);});//响应登录用户访问未授权路径时(user角色访问manager角色的接口) 有 未授权 json 提示exception.accessDeniedHandler((HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException)->{String json = JSON.toJSONString(Code.Forbidden);response.setContentType("application/json;charset=UTF-8");response.getWriter().println(json);});});/*会话管理*/http.sessionManagement(session -> {session//表示,最大连接数量为 1 ,同一个账号,最多只能在一台设备上登录,当第二个登陆时,会把第一个挤掉.maximumSessions(1)//挤掉后,对前端返回的json字符串.expiredSessionStrategy((SessionInformationExpiredEvent event)->{String json = JSON.toJSONString(Code.ForeignLogin);HttpServletResponse response = event.getResponse();response.setContentType("application/json;charset=UTF-8");response.getWriter().println(json);});});/*开启跨域访问*/http.cors(withDefaults());/* 禁用csrf的防御手段。* 开启后,相当于每次前端访问接口的时候* 都需要携带_crsf为参数名的参数,功能类似于 token,* 因此建议禁用* */http.csrf(AbstractHttpConfigurer::disable);return http.build();}//设置密码的编码方式(必须有)@Beanpublic BCryptPasswordEncoder bCryptPasswordEncoder(){return new BCryptPasswordEncoder(10); }
  • 解释 _scrf 在哪看,只有最初有,后面就没有,但是如果不携带,就不让你访问接口,因此建议禁用
2、DIYUsernamePasswordAuthenticationFilter
  • 该类用于替换 UsernamePasswordAuthenticationFilter 过滤器,应用自己自定义的过滤器
java">@Component  //相当于 UsernamePasswordAuthenticationFilter
public class DIYUsernamePasswordAuthenticationFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {/*问题:不能读取请求体中的信息,因为是一次性的,读完,后面就不能用了* 因此,这里避免用json格式传输 账号 和 密码* *///获取非 json 格式传输的,OK了,只要前端给 json 格式 的token就能获取了Map<String, String[]> parameterMap = request.getParameterMap(); //有前端打开SUser user = null;HttpSession session = request.getSession();//有前端打开//检查token,通过token解析出用户的账号,根据账号,从 session 中查询if(parameterMap.get("token") != null)user = (SUser)session.getAttribute(parameterMap.get("token")[0]);if (user == null) {//放行,表示已经退出,需要重新验证,区别就是有没有 存入SecurityContextHolder 一步骤filterChain.doFilter(request, response);return;}//存入SecurityContextHolder,获取权限信息封装到Authentication中UsernamePasswordAuthenticationToken authenticationToken =  // 没有前端获取用户数据目前先这样写new UsernamePasswordAuthenticationToken(user,user.getPassword(),user.getAuthorities());SecurityContextHolder.getContext().setAuthentication(authenticationToken);//验证成功,放行filterChain.doFilter(request, response);}
}
3、DIYAuthenticationProvider
  • 该类是发放授权的接口
java">@Component
public class DIYAuthenticationProvider implements AuthenticationProvider {@Resourceprivate UserDetailsService userDetailsService;@Resourceprivate PasswordEncoder passwordEncoder;@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {String username = authentication.getName();String password = (String) authentication.getCredentials();// 从数据库中加载用户信息UserDetails userDetails = userDetailsService.loadUserByUsername(username);// 检查密码是否正确if (!passwordEncoder.matches(password, userDetails.getPassword())) {throw new BadCredentialsException("用户名或密码错误");}// 创建一个已认证的 Authentication 对象UsernamePasswordAuthenticationToken authenticatedToken =new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities());authenticatedToken.setDetails(authentication.getDetails());return authenticatedToken;}@Overridepublic boolean supports(Class<?> authentication) {return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);}
}

4、DIYAuthenticationManager

  • 该类是用来调用发放授权接口
java">@Component
public class DIYAuthenticationManager implements AuthenticationManager {@Resource  //这里虽然是注入的接口,但是由于自定义的类 DIYAuthenticationProvider 实现了该接口,因此优先使用AuthenticationProvider authenticationProvider;//这里其实可以调用默认的 授权提供者,有匹配的就会授权,但是,没必要,因为肯定匹配不了,最后还是用自己的,@Override //那不如  直接就用自己的就好了public Authentication authenticate(Authentication authentication) throws AuthenticationException {return authenticationProvider.authenticate(authentication);}
}
5、MySQLUserDetailsManager
  • 该类用于获取用户的信息
java">@Component  //将这个类交给Spring容器管理,即:创建该类的 bean 对象,进而取代(重写)原来的方法
public class MySQLUserDetailsManager implements UserDetailsService{//由于是基于数据库的,因此,只需要实现一个 UserDetailsService 接口就好,不需要实现其他的接口@Resource //这个是Mapper接口,用于从数据库中调用查询信息SUserMapper sUserMapper; @Resource  //这个是必要的HttpServletRequest request; @Override                           //String usernamepublic UserDetails loadUserByUsername(String account) throws UsernameNotFoundException {//获取数据信息要在这里开始,由于只暴露用户输入account,因此数据库中的数据只能,所有的 account都不一样,才能唯一匹配 account,这里 Email 一定不一样//这里的 username 就是用户输入的账号,为了方便,就换一个变量名 accountList<SUser> sUsers = sUserMapper.selectAllByEmail(account);  //这里 Email 一定不一样if(sUsers != null && !sUsers.isEmpty()) {SUser sUser = sUsers.get(0);//这里把 authenticate 这个用户的信息存到session中,如果调用退出登录接口,就会删除session里面的内容HttpSession session = request.getSession();session.setAttribute(String.valueOf(sUser.getEmail()),sUser);return sUser;} else {throw new UsernameNotFoundException(account);}}
}
6、控制层
java">@Controller
@Tag(name = "登录注册")
@RequestMapping("/login")
public class LoginController {@Resourceprivate SUserService sUserService;@Resourceprivate AuthenticationManager authenticationManager;@GetMapping("/getLoginHTML")  //进入登录页的接口public String getLoginHtml(HttpSession session){boolean aNew = session.isNew();if(aNew)return "login";//如果一个浏览器试图登录两次,那么就会直接调用退出接口return "redirect:/goOut?TowLogin=true";}@PostMapping("/ooo")  //由于是自定义登录接口,因此什么请求都可以,建议用Post@ResponseBody  //将返回值写入响应体中public Code login(String account,String password){SUser sUser = new SUser();sUser.setEmail(account);sUser.setPassword(password);UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(sUser,password);Authentication authenticate = authenticationManager.authenticate(authenticationToken);if(Objects.isNull(authenticate))throw new AuthenticationCredentialsNotFoundException("用户账号或密码错误");else{//这里响应回去一个 token,根据账号加密后,生成的 tokenMap<String, String> map = new HashMap<>();map.put("token",authenticate.getName());return new Code<>(Code.OK, map);}
}
7、增强用户的实体类
  • 这里由于要封装用户的详细信息,而用 MybatisX 生成的 User 实体类不能满足需求,因此要实现一个接口
java">@TableName(value ="s_user")
@Data
@Repository  //将这个类交给IOC容器(Spring)管理
public class SUser implements Serializable , UserDetails{  //实现这个接口/*** 主键id,自动递增*/@TableId(type = IdType.AUTO)private Integer id;/*** 用户名:<=10*/private String name;/*** 年龄*/private Integer age;/*** 性别:女 , 男*/private String sex;/*** 邮箱账号:<=30*/private String email;/*** 密码:<=15*/private String password;/*** 是否被禁用:0-未禁用,1-已禁用*/private Integer isForbidden;/*** 该账号的角色:0-普通用户,1-管理员*/private String role;/*** 是否被删除(或用户注销):0-未删除,1-删除*/@TableLogicprivate Integer isDelete;@Serial@TableField(exist = false)private static final long serialVersionUID = 1L;@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {/*这里要自己拼接 ROLE_ + role* ROLE_ : 是固定的* 由于我这里的实体类设计的是:String role; 不是数组形式,因此不用循环* 如果是数组形式的限权,循环遍历,并创建 SimpleGrantedAuthority 就好了* */List<SimpleGrantedAuthority> list  = new ArrayList<>();list.add(new SimpleGrantedAuthority("ROLE_" + role));return list;}@Override  //注意:这里的用户名是 账号public String getUsername() {return this.email;}@Override//没有这个设定就返回通过的结果,可以用翻译 isAccountNonExpired ? 在每个方法名后加一个? 问自己是true/falsepublic boolean isAccountNonExpired() {return true;}@Override//自己的实体类中有这个设定,就返回判断的结果public boolean isAccountNonLocked() {return isForbidden == 1;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}
7、依赖
  • java版本 17
  • springBoot版本 3.2.0
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!--SpringSecurity依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><!--thymeleaf作为视图模板--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><!--mybatis-Puls的依赖--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.4.1</version><!--由于SpringBoot的版本太高,需要这样1--><exclusions><exclusion><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId></exclusion></exclusions></dependency><!--由于SpringBoot的版本太高,需要这样2--><dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>3.0.3</version></dependency><!--mysql的驱动包--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.30</version></dependency><!--简化实体类开发--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!--JavaWeb组件--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--引入json数据依赖,用于给前端返回json类型的数据--><dependency><groupId>com.alibaba.fastjson2</groupId><artifactId>fastjson2</artifactId><version>2.0.37</version></dependency><!--knife4j测试,对请求的测试,有两种,swagger-ui.html / doc.html 都可以--><dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId><version>4.4.0</version></dependency></dependencies>

http://www.ppmy.cn/server/130490.html

相关文章

使用kubeadm的方式部署k8s-1.22

一、环境准备 角色地址系统版本集群版本安装软件Master01192.168.89.164centos7.91.22.2kubeadm、kubelet、kubectl、docker、nginx、keepalivedMaster02192.168.89.165centos7.91.22.2kubeadm、kubelet、kubectl、docker、nginx、keepalivedMaster03192.168.89.166centos7.91…

Unity3D FixedUpdate处理物理模拟详解

前言 在Unity3D游戏开发中&#xff0c;FixedUpdate是一个特殊的方法&#xff0c;它专门用于处理物理模拟和与时间相关的逻辑。与Update方法不同&#xff0c;FixedUpdate是以固定的时间间隔被调用的&#xff0c;这个间隔可以在Unity编辑器中的“项目设置”&#xff08;Project …

git将本地分支推送至远程

要将本地分支推送到远程仓库&#xff0c;你可以按照以下步骤操作。假设你已经安装了 Git 并且本地仓库已经初始化。 1. 配置远程仓库&#xff08;如果尚未配置&#xff09; 首先&#xff0c;你需要确保你的本地仓库已经关联了一个远程仓库。如果还没有关联&#xff0c;你可以…

LabVIEW开关磁阻电机特性测量系统

基于LabVIEW软件和特定硬件组件的开关磁阻电机&#xff08;SRM&#xff09;特性测量系统&#xff0c;结合多功能数据采集卡&#xff0c;统能够准确地测量并分析SRM的电磁特性&#xff0c;从而支持电机模型的精确建立和性能优化。 项目背景 在工业生产和家用电器领域&#xff0…

什么是SQLite?

一、什么是SQLite? SQLite是一个进程内的软件库&#xff0c;实现了自给自足的、无服务器的、零配置的、事务性的SQL数据库引擎。它是一个零配置的数据库&#xff0c;这意味着与其他数据库不一样&#xff0c;您不需要在系统中配置。 就像其它数据库&#xff0c;SQLite引擎不是…

3种常用的缓存读写策略详解

在详解3种常用的缓存读写之前&#xff0c;我们先要了解什么事缓存读写。 缓存读写是指在使用缓存技术时&#xff0c;对数据进行读取和更新的操作过程。缓存是一种用于提高系统性能和可扩展性的技术&#xff0c;通过减少对慢速存储&#xff08;如数据库&#xff09;的访问次数&…

【RTCP】报文学习笔记

在学习中,发现每一篇都只能窥探其中一部分内容。因此学习了多个大神的文章,记录如下: 参考希望_睿智 大神的文章:从零开始精通RTSP之深入理解RTCP协议, 大神对于细节表述非常到位。 read_book/RTP_RTCP /RTP_RTCP协议内容–精选自译.md 大神提供了更多更为详细的信息。 ZL…

npm ERR! Object for dependency “loader-utils“ is empty.

错误提示 npm ERR! Object for dependency "loader-utils" is empty.npm ERR! Something went wrong. Regenerate the package-lock.json with "npm install".npm ERR! If using a shrinkwrap, regenerate with "npm shrinkwrap".错误原因 在安…