SpringSecurity实现自定义登录接口

news2024/10/6 7:21:51

SpringSecurity实现自定义登录接口

1、配置类 ConfigClazz(SpringSecuriey的)
    //首先就是要有一个配置类
	@Resource
    private DIYUsernamePasswordAuthenticationFilter diyUsernamePasswordAuthenticationFilter;

    /*SpringSecurity配置*/
    @Bean
    public 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();
    }

	//设置密码的编码方式(必须有)
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder(){
        return new BCryptPasswordEncoder(10); 
    }

  • 解释 _scrf 在哪看,只有最初有,后面就没有,但是如果不携带,就不让你访问接口,因此建议禁用
2、DIYUsernamePasswordAuthenticationFilter
  • 该类用于替换 UsernamePasswordAuthenticationFilter 过滤器,应用自己自定义的过滤器
@Component  //相当于 UsernamePasswordAuthenticationFilter
public class DIYUsernamePasswordAuthenticationFilter extends OncePerRequestFilter {

@Override
    protected 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
  • 该类是发放授权的接口
@Component
public class DIYAuthenticationProvider implements AuthenticationProvider {

    @Resource
    private UserDetailsService userDetailsService;
    @Resource
    private PasswordEncoder passwordEncoder;

    @Override
    public 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;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }
}

4、DIYAuthenticationManager

  • 该类是用来调用发放授权接口
@Component
public class DIYAuthenticationManager implements AuthenticationManager {

    @Resource  //这里虽然是注入的接口,但是由于自定义的类 DIYAuthenticationProvider 实现了该接口,因此优先使用
    AuthenticationProvider authenticationProvider;

      		  //这里其实可以调用默认的 授权提供者,有匹配的就会授权,但是,没必要,因为肯定匹配不了,最后还是用自己的,
    @Override //那不如  直接就用自己的就好了
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        return authenticationProvider.authenticate(authentication);
    }
}
5、MySQLUserDetailsManager
  • 该类用于获取用户的信息
@Component  //将这个类交给Spring容器管理,即:创建该类的 bean 对象,进而取代(重写)原来的方法
public class MySQLUserDetailsManager implements UserDetailsService{
    //由于是基于数据库的,因此,只需要实现一个 UserDetailsService 接口就好,不需要实现其他的接口

    @Resource //这个是Mapper接口,用于从数据库中调用查询信息
    SUserMapper sUserMapper; 
    @Resource  //这个是必要的
    HttpServletRequest request; 

    @Override                           //String username
    public UserDetails loadUserByUsername(String account) throws UsernameNotFoundException {
        //获取数据信息要在这里开始,由于只暴露用户输入account,因此数据库中的数据只能,所有的 account都不一样,才能唯一匹配 account,这里 Email 一定不一样
        //这里的 username 就是用户输入的账号,为了方便,就换一个变量名 account
        List<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、控制层
@Controller
@Tag(name = "登录注册")
@RequestMapping("/login")
public class LoginController {

    @Resource
    private SUserService sUserService;
    @Resource
    private 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,根据账号加密后,生成的 token
            Map<String, String> map = new HashMap<>();
            map.put("token",authenticate.getName());
            return new Code<>(Code.OK, map);
        }
}
7、增强用户的实体类
  • 这里由于要封装用户的详细信息,而用 MybatisX 生成的 User 实体类不能满足需求,因此要实现一个接口
@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-删除
     */
    @TableLogic
    private Integer isDelete;

    @Serial
    @TableField(exist = false)
    private static final long serialVersionUID = 1L;

    @Override
    public 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/false
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override//自己的实体类中有这个设定,就返回判断的结果
    public boolean isAccountNonLocked() {
        return isForbidden == 1;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public 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.coloradmin.cn/o/2191393.html

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

相关文章

Android2024.2.1升级错误

提示 Gradle 版本不兼容&#xff0c;升级后就报错了 。 1.gradle安装包镜像 distributionBaseGRADLE_USER_HOME distributionPathwrapper/dists //distributionUrlhttps\://services.gradle.org/distributions/gradle-8.5-bin.zip distributionUrlhttps://mirrors.cloud.tencen…

Koa2项目实战1(项目搭建)

前言 在正式开始之前&#xff0c;需要先知道用到的东西&#xff1a; koa&#xff1a;Koa 是一个基于 Node.js 的 Web 应用框架&#xff0c;非常适合开发API服务&#xff0c;可以与前端框架&#xff08;如 Vue.js、React.js&#xff09;结合使用&#xff0c;实现前后端分离的开…

第八篇:磁盘管理(1)

目录 6.1分区 6.1.1基本分区 6.1.1.1磁盘的相关知识 6.1.1.1.1基础知识 6.1.1.1.2命名 1.对于串口硬盘&#xff1a;/dev/sda、/dev/sdb、/dev/sdc......往后都是一个字母一个字母的累加 2.对于并口硬盘&#xff1a;/dev/hda其余相同 6.1.1.1.3磁盘的分区方式 方式1&am…

c++_ 多态

目录 一.多态 1.1多态(polymorphism)的概念 1.2实现多态还有两个必须重要条件&#xff1a; 1.3 重载 和 虚函数的重写/覆盖 和 隐藏 的比对 1.4 协变(了解) 1.5 析构函数的重写 1.6 override 和final关键字 二.纯虚函数和抽象类 三. 多态的原理 3.1虚函数表指针 3.…

黑马JavaWeb开发跟学(十一)SpringBootWeb案例

黑马JavaWeb开发跟学十一.SpringBootWeb案例 SpringBootWeb案例1. 新增员工1.1 需求1.2 接口文档1.3 思路分析1.4 功能开发1.5 功能测试1.6 前后端联调 2. 文件上传2.1 简介2.2 本地存储2.3 阿里云OSS2.3.1 准备2.3.2 入门2.3.3 集成 3. 修改员工3.1 查询回显3.1.1 接口文档3.1…

性能测试笔记2-总

安装路径&#xff1a;先装jdk,后装JMeter 安装JDK&#xff1a; 下载JDK – 安装JDK – 配置环境变量 – 验证 安装Jmeter&#xff1a; 下载Jmeter – 安装Jmeter – 配置环境变量 – 启动验证 注意点&#xff1a; 下载JDK时&#xff0c;注意电脑操作系统是32位/64位 下载…

力扣 简单 110.平衡二叉树

文章目录 题目介绍解法 题目介绍 解法 平衡二叉树:任意节点的左子树和右子树的高度之差的绝对值不超过 1 //利用递归方法自顶向下判断以每个节点为根节点的左右子树的最大深度是否大于1 class Solution {public boolean isBalanced(TreeNode root) {if(root null){return tr…

Uniapp API

1.uni.showToast 显示消息提示框 unishowToast({ obj参数 }) 2.uni.showLoading 显示 loading 提示框, 需主动调用 uni.hideLoading 才能关闭提示框。 3.uni.showModal 显示模态弹窗&#xff0c;可以只有一个确定按钮&#xff0c;也可以同时有确定和取消按钮。类似于一个A…

windows上安装python环境

前言 最近电脑重装了系统&#xff0c;需要重新安装python环境 &#xff0c;因此记录一下 1.下载 打开python官网下载&#xff0c;下载链接&#xff1a;https://www.python.org/downloads/windows/ 点击下载 &#xff0c;我这里使用64位操作系统(大部分电脑)&#xff0c;根据…

怎么查看网站是否被谷歌收录,查看网站是否被谷歌收录的简便方法

查看网站是否被谷歌收录&#xff0c;有多种简便方法可供选择。以下是一些常用的简便方法&#xff1a; 一、使用“site:”指令 打开谷歌搜索引擎&#xff1a; 在浏览器中打开Google.com&#xff0c;确保使用的是谷歌的官方搜索引擎。 输入查询指令&#xff1a; 在搜索框中输…

网关路由登录校验

网关过滤器 登录校验必须在请求转发到微服务之前做&#xff0c;否则就失去了意义。而网关的请求转发是Gateway内部代码实现的&#xff0c;要想在请求转发之前做登录校验&#xff0c;就必须了解Gateway内部工作的基本原理。 暂时无法在飞书文档外展示此内容 如图所示&#xff…

15分钟学 Python 第38天 :Python 爬虫入门(四)

Day38 : Python爬虫异常处理与反爬虫机制 章节1&#xff1a;异常处理的重要性 在爬虫开发过程中&#xff0c;网络请求和数据解析常常会遭遇各种异常。正确的异常处理可以提高程序的稳定性&#xff0c;避免崩溃&#xff0c;并帮助开发者快速定位问题。 章节2&#xff1a;常见…

requests案例——腾讯新闻数据的爬取

需求&#xff1a; 1.利用requests方法爬取该i.news.qq.com网站的数据&#xff08;包括名字和对应链接&#xff09; 2.实现翻页的爬取 3.将爬取下来的数据保存在excel文件中 4.利用jsonpath来解析获取的数据 5.使用openpyxl库处理 Excel 文件 注意&#xff1a; 1.如果报以…

一个不错的 SQL 编码风格的指南

前言 SQL语句的编写对于我们后端开发者而言是一个必备的技巧&#xff0c;在日常工作中&#xff0c;SQL语言编写的质量不仅仅会影响到团队的合作效率与项目的可维护性&#xff0c;还直接关系到数据库的性能优化与数据安全。今天大姚给大家分享一个不错的 SQL 编码风格的指南&am…

卡码网KamaCoder 117. 软件构建

题目来源&#xff1a;117. 软件构建 C题解&#xff08;来源代码随想录&#xff09;&#xff1a;拓扑排序&#xff1a;给出一个 有向图&#xff0c;把这个有向图转成线性的排序。拓扑排序也是图论中判断有向无环图的常用方法。 拓扑排序的过程&#xff0c;其实就两步&#xff1…

Kubernetes资源详解

华子目录 1.Kubernetes中的资源1.1资源管理介绍1.2资源管理方式1.2.1命令式对象管理1.2.2kubectl常见command命令1.2.3资源类型1.2.4常用资源类型 基本命令示例运行和调试命令示例高级命令示例总结 其他命令示例create和apply区别案例显示命名空间查看命名空间中的pod如何对外暴…

数据库伸缩设计-分库分表如何做?读书笔记

一些企业内部系统&#xff0c;用户数量和业务规模有限&#xff0c;因此并不会产生巨大的数据量&#xff0c;这时数据库的存储和读写性能均不会成为瓶颈&#xff0c;没有扩容的需要&#xff0c;因此无须考虑伸缩性。对于一些互联网系统&#xff0c;前后端应用可以通过CDN 、缓存…

基于Zynq SDIO WiFi移植二(支持2.4/5G)

1 SDIO设备识别 经过编译&#xff0c;将移植好的uboot、kernel、rootFS、ramdisk等烧录到Flash中&#xff0c;上电启动&#xff0c;在log中&#xff0c;可看到sdio设备 [ 1.747059] mmc1: queuing unknown CIS tuple 0x01 (3 bytes) [ 1.761842] mmc1: queuing unknown…

vite学习教程06、vite.config.js配置

前言 博主介绍&#xff1a;✌目前全网粉丝3W&#xff0c;csdn博客专家、Java领域优质创作者&#xff0c;博客之星、阿里云平台优质作者、专注于Java后端技术领域。 涵盖技术内容&#xff1a;Java后端、大数据、算法、分布式微服务、中间件、前端、运维等。 博主所有博客文件…

Meta MovieGen AI:颠覆性的文本生成视频技术详解

近年来&#xff0c;生成式AI技术的发展迅猛&#xff0c;尤其是在文本生成图像、文本生成视频等领域。Meta公司近期推出的MovieGen AI&#xff0c;以其强大的文本生成视频能力震撼了整个AI行业。本文将详细解读Meta MovieGen AI的核心技术、功能特性及其在实际应用中的潜力。 一…