• SpringSecurity简明教程


    大致分为三部分:数据库认证,自定义登录页,自定义过滤器
    SpringSecurity主要实现UserDetailsService来验证登录的用户信息,和Security的配置类来对登录方式和资源进行限制。
    案例包含利用数据库进行登录验证、URL访问限制、自定义登录页和利用ajax方式登录、实现自定义过滤器对验证码进行验证,完整代码在https://github.com/say-hey/springboot-security-concise

    SprigSecurity接口

    UserDetails

    • 接口:表示用户信息,账号:密码:是否过期:是否锁定:证书是否过期:权限集合
    • 实现类:User
      自定义类实现UserDetails接口,作为系统中的用户类,这个类可以交给SpringSecurity使用
    需要自定义的User类继承UserDetails,然后实现方法,但在某些案例中也没有继承
    同时在数据库中添加相应字段,如是否过期是否锁定等
    /**
     * 用户表
     * 用户表和角色表的对应关系,
     */
    
    @Data
    @Entity
    @AllArgsConstructor
    @NoArgsConstructor
    @Table(name = "user")
    // 自定义的User可以实现 implements UserDetails 接口,需要完成方法如是否可用,是否锁定,是否过期,角色集合等,同时在数据库中添加这些字段
    // 实现这个方法可用于扩展,也可以不实现
    public class User implements UserDetails{
    
        @Id
        // 主键自动增长
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @Column(name = "id")
        Integer id;
    
        @Column(name = "username")
        String username;
    
        @Column(name = "password")
        String password;
    
        // 过期
        @Column(name = "isAccountNonExpired")
        Boolean isAccountNonExpired;
        // 锁定
        @Column(name = "isAccountNonLocked")
        Boolean isAccountNonLocked;
        // 凭证
        @Column(name = "isCredentialsNonExpired")
        Boolean isCredentialsNonExpired;
        // 启用
        @Column(name = "isEnabled")
        Boolean isEnabled;
        // 权限
        // List authorities;
    
    
        /**
         * 多对多关系会在创建用户和新角色时级联新增,关联表为user_role,当前对象在关联表对应的外键,和另一方在关联表中对应的外键
         * cascade:级联操作,如保存、删除时级联的行为
         * joinColumns:在关联表中的外键名
         * inverseJoinColumns:另一方在关联表中的外键名
         */
        @ManyToMany(targetEntity = Role.class, cascade = CascadeType.MERGE, fetch = FetchType.LAZY)
        @JoinTable(name = "user_role",
                joinColumns = {@JoinColumn(name = "u_id", referencedColumnName = "id")},
                inverseJoinColumns = {@JoinColumn(name = "r_id", referencedColumnName = "id")})
        List roles = new ArrayList<>();
    
    
        /**
         * 重写toString()方法,否则在sout输出时,会导致两个对象的toString()相互调用,现在需要去掉一方的关联字段输出
         * java.lang.StackOverflowError
         * @return
         */
        @Override
        public String toString() {
            return "User{" +
                    "id=" + id +
                    ", username='" + username + '\'' +
                    ", password='" + password + '\'' +
                    ", isAccountNonExpired=" + isAccountNonExpired +
                    ", isAccountNonLocked=" + isAccountNonLocked +
                    ", isCredentialsNonExpired=" + isCredentialsNonExpired +
                    ", isEnabled=" + isEnabled +
                    ", roles=" + roles +
                    '}';
        }
    
    
        // 实现UserDetails后的方法
    
        /**
         * 获取权限,这里使用的是GrantedAuthority类,在UserDetailsService中出现,用于组装角色权限信息
         *
         * roles: [Role{id=1, role='Cat'}, Role{id=2, role='Dog'}]
         * authorities: [ROLE_Dog, ROLE_Cat]
         *
         * @return
         */
        @Override
        public Collection getAuthorities() {
    
            List roles = this.getRoles();
            Set authorities = new HashSet<>();
            for (Role role : roles) {
                authorities.add(new SimpleGrantedAuthority("ROLE_"+role.getRole()));
            }
    
            return authorities;
        }
    
        /**
         * 账户是否过期
         * @return
         */
        @Override
        public boolean isAccountNonExpired() {
            return false;
        }
    
        /**
         * 账户是否锁定
         * @return
         */
        @Override
        public boolean isAccountNonLocked() {
            return false;
        }
    
        /**
         * 凭证是否过期
         * @return
         */
        @Override
        public boolean isCredentialsNonExpired() {
            return false;
        }
    
        /**
         * 是否启用
         * @return
         */
        @Override
        public boolean isEnabled() {
            return false;
        }
    }

    UserDetailsService

    • 接口:获取用户信息,得到UserDetails对象,一般项目要自定义类实现这个接口,从数据库中获取数据
    • 实现一个方法:loadUserByUsername()根据用户名,获取用户信息(用户名称,密码,角色集合,是否可用等)
    • 实现类:UserDetailsManager接口{InMemoryUserDetailsManager,JdbcUserDetailsManager)基于内存和数据库
    具体实现查看下一小节

    数据库认证

    1. SpringSecurity中有一个UserDetail接口,高度抽象用户信息类,它返回一个User类,和自定义user内容相似,包括username,password,authorities(角色、权限,继承GrantedAuthority)集合

    2. 其中,角色和权限内容表达不同,角色:admin权限:ROLE_ADMIN

    3. 实现接口UserDetailService接口,完成loadUserByUsername方法,返回User

    4. SpringSecurity在登录时会自动调用方法,去数据库中查询出数据并验证

    @Service
    public class SecurityUserDetailsServiceImpl implements UserDetailsService {    
    
    
    	@Transactional
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            User user = userRepository.findUserByUsername(username);
            if(user == null){
                throw new UsernameNotFoundException("用户 " + username + " 登录失败,用户名不存在!");
            }
            // System.out.println("登录用户:" + ((Role)user.getRoles()).getRole());
    
    
            // 方式一:添加权限
            List roles = user.getRoles();
            Set authorities = new HashSet<>();
            for (Role role : roles) {
                authorities.add(new SimpleGrantedAuthority("ROLE_"+role.getRole()));
            }
            // 方法二:在自定义的User实现UserDetails后,利用上方方式实现getAuthorities()方法,直接返回
            Collection authorities1 = user.getAuthorities();
    
            // 权限和角色在前缀上不同,权限会自动加上前缀ROLE_,roles()方法点进去就是GrantedAuthority
            // GrantedAuthority : ROLE_admin
            // Role : admin
    
            return org.springframework.security.core.userdetails.User
                    .withUsername(user.getUsername())
                    .password(user.getPassword())
                    .authorities(authorities)
                    .build();
        }
    }
    对于role和authorities:
    roles: [Role{id=1, role='Cat'}, Role{id=2, role='Dog'}]
    authorities: [ROLE_Dog, ROLE_Cat]

    URL权限

    实现数据库认证之后,设置URL权限,就可以在网页进行权限控制

    方式一:旧方式,使用默认登录页,在实现SecurityConfigurerAdapter类的cofnigure(HttpSecurity)方法中设置

    // 实现SecurityConfigurerAdapter类
    
    	public void configure(HttpSecurity http){
    		http.authorizeHttpRequests()
    			.requestMatchers("/home").hasRole("USER")
    			.requestMatchers("/home/l1/**").hasRole("Dog")
    			.requestMatchers("/home/l2/**").hasRole("Cat")
    			.and()
    			.formLogin();
    	}
    	// “formLogin()”已弃用并标记为删除

    方式二:在SecurityConfig配置类中注入配置HttpSecurity

        @Bean
        public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
            return http
                    .authorizeHttpRequests(auth->{
    
                        // 设置url权限,注意所有权限的配置顺序
                        auth.requestMatchers("/home").permitAll();
                        auth.requestMatchers("/home/l0").hasRole("USER");
                        auth.requestMatchers("/home/l1/**").hasRole("Dog");
                        auth.requestMatchers("/home/l2/**").hasRole("Cat");
                        auth.anyRequest().authenticated();
                    })
                    .build();
        }

    请求链接

        <h2>Welcome Homeh2>
        
        <a href="/home/l0">a dog/cata><br>
        <a href="/home/l1">a doga><br>
        <a href="/home/l2">a cata><br>

    Controller

    @RestController
    public class HomeController {
    
        @GetMapping("/home/l0")
        public String l0(){
            return "you is a dog/cat";
        }
    
        @GetMapping("/home/l1")
        public String l1(){
            return "you is a dog";
        }
    
        @GetMapping("/home/l2")
        public String l2(){
            return "you is a cat";
        }
    }

    自定义登录页

    查看过滤器类UsernamePasswordAuthenticationFilter,里面设置了默认的登录页的信息,只要规则匹配就会自动验证登录信息

    public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
        public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
        public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
        private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/login", "POST");
        private String usernameParameter = "username";
        private String passwordParameter = "password";
        private boolean postOnly = true;
        // ...
    }

    自定义登录页的标签也要用username,password属性

    
    
    "@{/login}" method="post">
    "text" name="username" placeholder="Username"/>
    "password" name="password" placeholder="Password"/>
    "submit" value="Log in" />

    配置security,注入HttpSecurity参数

        @Bean
        public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
            return http
                    .authorizeHttpRequests(auth->{
    
                        // 设置url权限,注意所有权限的配置顺序
                        auth.requestMatchers("/home").permitAll();
                        auth.requestMatchers("/home/l0").hasRole("USER");
                        auth.requestMatchers("/home/l1/**").hasRole("Dog");
                        auth.requestMatchers("/home/l2/**").hasRole("Cat");
                        auth.anyRequest().authenticated();
                    })
                    .formLogin(conf->{
                        // 自定义表单登录页,这个是网页
                        // https://docs.spring.io/spring-security/reference/servlet/authentication/passwords/form.html
                        conf.loginPage("/login");
                        // 表单登录请求,这个是url请求
                        conf.loginProcessingUrl("/login");
                        // 默认登录成功页
                        conf.defaultSuccessUrl("/home");
                        // 登录相关请求不需要认证
                        conf.permitAll();
                    })
                    .logout(conf->{
                        // 登出请求
                        conf.logoutUrl("/logout");
                        conf.logoutSuccessUrl("/login");
                        conf.permitAll();
                    })
                    // 使用自定义的userDetails认证过程,
                    // .userDetailsService(null)
                    .csrf(AbstractHttpConfigurer::disable)// 关闭跨站请求伪造保护功能
                    .build();
        }

    AJAX登录

    1. 前后端分离,使用ajax登录,传递json数据,用户发送请求,spring security接受数据并验证,然后返回json给用户
    2. 还可以在security中配置成功和失败的处理器
      登录页
        <script type="text/javascript" src="/js/jquery-3.7.0.min.js">script>
        <script type="text/javascript">
            $(function (){
                $("#btnLogin").click(function () {
                    console.log("ajax")
                    var uname = $("#username").val();
                    var pwd = $("#password").val();
                    $.ajax({
                        url:"/login",
                        type:"POST",
                        data:{
                            "username":uname,
                            "password":pwd
                        },
                        dataType:"json",
                        success:function (res) {
                            alert(res.status +":"+res.msg)
                        }
                    })
                })
            })
        script>
        
        
    	<div>
            使用Ajax登录,json传递数据<br>
            用户名:<input type="text" id="username"><br>
     码:<input type="password" id="password"><br>
            <button id="btnLogin">登录button><br>
        div>

    在security配置类中通过静态资源认证

            // 静态资源
            auth.requestMatchers("/js/**").permitAll();

    自定义处理器

    认证处理器,自定义请求认证成功或失败后的动作

    /**
     * security登录认证成功处理器
     */
    @Component
    public class SecurityAuthSuccessHandler implements AuthenticationSuccessHandler {
        /**
         * 验证成功后执行
         * @param request 请求对象
         * @param response 响应对象
         * @param authentication security验证成功后的封装对象,包括用户的信息
         * @throws IOException
         * @throws ServletException
         */
        @Override
        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
            // 登录的用户验证成功后执行
            response.setContentType("text/json;charset=utf-8");
            PrintWriter writer = response.getWriter();
            writer.println("{\"msg\":\"登录成功!\"}");
            writer.flush();
            writer.close();
        }
    }
    /**
     * security登录认证失败处理器
     */
    @Component
    public class SecurityAuthFailureHandler implements AuthenticationFailureHandler {
        @Override
        public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
            // 登录的用户验证失败后执行
            response.setContentType("text/json;charset=utf-8");
            PrintWriter writer = response.getWriter();
            writer.println("{\"msg\":\"登录失败(用户名或密码错误)!\"}");
            writer.flush();
            writer.close();
        }
    }

    在security配置类中通过静态资源认证

             // 静态资源
            auth.requestMatchers("/js/**").permitAll();

    注意,使用了handler处理器,就不要设置默认登录页,否则不起作用

            // 使用handler类
            conf.successHandler(successHandler);
            conf.failureHandler(failureHandler);
            // 默认登录成功页,使用了handler,就不要使用默认登录页,否则handler不起作用
            // conf.defaultSuccessUrl("/home");

    使用JSON格式

    在用ajax的过程中使用json传递数据
    创建vo类对象,传递数据

    @Data
    public class Result {
        // 0成功 1失败
        Integer code;
        // 200 成功 500失败
        Integer status;
        // 消息
        String msg;
    }

    处理器

    @Component
    public class SecurityAuthSuccessHandler implements AuthenticationSuccessHandler {
        /**
         * 验证成功后执行
         * @param request 请求对象
         * @param response 响应对象
         * @param authentication security验证成功后的封装对象,包括用户的信息
         * @throws IOException
         * @throws ServletException
         */
        @Override
        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
            // 登录的用户验证成功后执行
            response.setContentType("text/json;charset=utf-8");
    
            Result result = new Result();
            result.setCode(0);
            result.setStatus(200);
            result.setMsg("登录成功");
            // 使用jsckson
            ObjectMapper mapper = new ObjectMapper();
            ServletOutputStream outputStream = response.getOutputStream();
            mapper.writeValue(outputStream, result);
    
            outputStream.flush();
            outputStream.close();
    
            // PrintWriter writer = response.getWriter();
            // writer.println("{\"msg\":\"登录成功!\"}");
            // writer.flush();
            // writer.close();
        }
    }

    验证码

    在用户名和密码下方添加验证码输入,在controller中生成验证码图片,然后响应给网页

    /**
     * 生成验证码响应到页面
     */
    @Controller
    @RequestMapping("/captcha")
    public class ChptchaController {
    
        // 生成验证码的属性
        // 宽度
        private int width = 120;
        // 高度
        private int height = 30;
        // 内容在图片中的起始位置
        private int drawY = 20;
        // 文字的间隔
        private int space = 15;
        // 验证码文字个数
        private int charCount = 6;
        // 验证码内容数组 注意数字0和字母O容易混淆,最好注释掉
        private String chars[] = {"A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P"
                ,"Q","R","S","T","U","V","W","X","Y","Z","0","1","2","3","4","5","6","7","8","9"};
    
        /**
         * 绘制一个图片,将图片响应给请求
         * @param request
         * @param response
         */
        @GetMapping("/code")
        public void makeCaptchaCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
    
            // 创建一个背景透明的图片,图片格式使用rgb表示颜色,画布
            BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
            // 获取画笔
            Graphics graphics = image.getGraphics();
            // 设置画笔颜色 白色
            graphics.setColor(Color.white);
            // 把画布涂成白色 fillRect(矩形的起始x,矩形的起始y,矩形的宽度,矩形的高度)
            graphics.fillRect(0, 0, width, height);
    
            // 画内容
            // 创建字体
            Font font = new Font("宋体", Font.BOLD, 18);
            // 画笔设置字体和颜色
            graphics.setFont(font);
            graphics.setColor(Color.black);
            // 获取随机值
            int ran = 0;
            int len = chars.length;
            StringBuffer stringBuffer = new StringBuffer();
            for(int i = 0; i < charCount; i++){
                ran = new Random().nextInt(len);
                // 保存随机值
                stringBuffer.append(chars[ran]);
                // 设置随机颜色
                graphics.setColor(randomColor());
                // 画的内容,间隔,起始
                graphics.drawString(chars[ran], (i+1)*space, drawY);
            }
            // 绘制干扰线
            for(int i = 0; i < 4; i++){
                graphics.setColor(randomColor());
                int line[] = randomLine();
                graphics.drawLine(line[0], line[1], line[2], line[3]);
            }
    
            // 生成的验证码存到session
            request.getSession().setAttribute("code", stringBuffer.toString());
            System.out.println("captcha: " + stringBuffer.toString());
    
            // 设置响应没有缓存
            response.setHeader("Pragma", "no-cache");
            response.setHeader("Cache-Control", "no-cache");
            response.setDateHeader("Expires", 0);
            // 设置响应格式
            response.setContentType("image/png");
    
            // 输出图像 w(输出的图像,图像格式,输出到哪)
            ServletOutputStream outputStream = response.getOutputStream();
            ImageIO.write(image, "png", outputStream);
            outputStream.flush();
            outputStream.close();
        }
    
        /**
         * 生成随机颜色
         * @return
         */
        public Color randomColor(){
            Random random = new Random();
            int r = random.nextInt(255);
            int g = random.nextInt(255);
            int b = random.nextInt(255);
            return new Color(r, g, b);
        }
    
        /**
         * 生成干扰线的随机起始点
         * @return
         */
        public int[] randomLine(){
            Random random = new Random();
            int x1 = random.nextInt(width/2);
            int y1 = random.nextInt(height);
            int x2 = random.nextInt(width);
            int y2 = random.nextInt(height);
            return new int[]{x1, y1, x2, y2};
        }
    }

    通过验证

                        // 验证码
                        auth.requestMatchers("/captcha/**").permitAll();

    在前端页面添加验证码

        <script type="text/javascript" src="/js/jquery-3.7.0.min.js">script>
        <script type="text/javascript">
            $(function (){
                $("#btnLogin").click(function () {
                    console.log("ajax")
                    var uname = $("#username").val();
                    var pwd = $("#password").val();
                    // 用户输入验证码
                    var textcode = $("#textcode").val();
    
                    $.ajax({
                        url:"/login",
                        type:"POST",
                        data:{
                            "username":uname,
                            "password":pwd,
                            "code":textcode
                        },
                        dataType:"json",
                        success:function (res) {
                            alert(res.status +":"+res.msg)
                        }
                    })
                })
            })
    
            function changeCode(){
                var url = "/captcha/code?t=" + new Date();
                $("#imageCode").attr("src", url);
            }
        script>
        
        // ...
        
        <div>
            使用Ajax登录,json传递数据<br>
            用户名:<input type="text" id="username"><br>
     码:<input type="password" id="password"><br>
            验证码:<input type="text" id="textcode">
            <img src="/captcha/code" id="imageCode"/>
            <a href="javascript:void(0)" onclick="changeCode()">重新获取a><br>
            <button id="btnLogin">登录button><br>
        div>

    异常

    验证码异常处理,在过滤器处理验证码之前

    /**
     * 验证码异常处理,在过滤器处理验证码之前
     */
    public class VerificationException extends AuthenticationException {
    
        public VerificationException(){
            super("验证码错误,请重新输入!");
        }
    }

    过滤器

    概述

    Security中有很多过滤器,例如表单登录验证使用的UsernamePasswordAuthenticationFilter,而验证码在表单登录验证之前使用,所以需要自定义一个过滤器,然后放入整个过滤器链中,并且在UsernamePasswordAuthenticationFilter之前

    自定义过滤器

    使用OncePerRequestFilter,一次性过滤器,出现异常调用handler处理器

    /**
     * 验证码过滤器,使用在UsernamePasswordAuthenticationFilter之前
     */
    public class VerificationFilter extends OncePerRequestFilter {
    
        // 登录失败的handler,在过滤器抛出异常时使用
        private SecurityAuthFailureHandler failureHandler = new SecurityAuthFailureHandler();
    
        /**
         * 验证码过滤器
         * @param request
         * @param response
         * @param filterChain
         * @throws ServletException
         * @throws IOException
         */
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    
            // 缺少登录成功,错误提示有问题!!原因是设置了defaultSuccessUrl(),同样的有failurehandler也不要设置默认的错误页
    
            // 验证码只在登录的过程中才使用这个过滤器
            String requestURI = request.getRequestURI();
            // 如果登录页和表单登录请求都使用/login,那么此处要判断是去登录页(GET)还是表单登录请求(POST)
            String method = request.getMethod();
            if(!"/login".equals(requestURI) || "GET".equals(method)){
                // 不是登录操作,不经过这个过滤器
                filterChain.doFilter(request, response);
            }else{
                try{
                    // 验证验证码
                    verificationCode(request);
                    // 通过
                    filterChain.doFilter(request, response);
                }catch (VerificationException e){
                    // 验证出现异常时,跳转到表单登录失败的处理器SecurityAuthFailureHandler中
                    // 1.在filter中添加handler属性,在这里调用
                    // 2.在SecurityAuthFailureHandler中修改,添加一个vo.Result属性,然后判断是正常的handler还是第三方异常跳转过去的
                    Result result = new Result();
                    result.setCode(1);
                    result.setStatus(501);
                    result.setMsg("验证码错误,请重新输入!!");
                    failureHandler.setResult(result);
                    failureHandler.onAuthenticationFailure(request, response, e);
                }
    
            }
    
    
        }
    
        private void verificationCode(HttpServletRequest request) throws VerificationException {
            // 获取请求中的验证码Code
            String requestCode = request.getParameter("code");
            // 获取session中的验证码Code
            String sessionCode = "";
            HttpSession session = request.getSession();
            Object code = session.getAttribute("code");
            if(code != null){
                sessionCode = (String) code;
            }
    
            System.out.println("Verificate Captcha: session:" + sessionCode + " |request:" + requestCode);
    
            // 一次性验证码,使用后销毁
            if(!StringUtils.isEmpty(sessionCode)){
                // 能获取到session中的验证码,说明已经在页面生成了,现在就不能再用了
                session.removeAttribute("code");
            }
    
            // 判断验证码code是否正确
            if(StringUtils.isEmpty(requestCode) || StringUtils.isEmpty(sessionCode) || !requestCode.equals(sessionCode)){
                // 验证失败
                throw new VerificationException();
            }
        }
    }

    修改handler处理器,判断一下是否第三方调用(如验证码异常)

    /**
     * security登录认证失败处理器
     */
    @Component
    public class SecurityAuthFailureHandler implements AuthenticationFailureHandler {
    
        // 添加result属性,可以让第三方异常调用,展示异常信息
        private Result result;
        public Result getResult() {
            return result;
        }
        public void setResult(Result result) {
            this.result = result;
        }
    
        /**
         * 验证失败后执行
         * @param request 请求对象
         * @param response 响应对象
         * @param exception security验证失败后的封装对象,包括用户的信息
         * @throws IOException
         * @throws ServletException
         */
        @Override
        public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
            // 登录的用户验证失败后执行
            response.setContentType("text/json;charset=utf-8");
            System.out.println("failure handler...");
    
            // 判断是否自定义的result,还是第三方异常调用的result,第三方异常调用时,result已经有值了
            if(result == null){
                Result localResult = new Result();
                localResult.setCode(1);
                localResult.setStatus(500);
                localResult.setMsg("登录失败(用户名或密码错误)!");
                result = localResult;
            }
    
            // 使用jsckson
            ObjectMapper mapper = new ObjectMapper();
            ServletOutputStream outputStream = response.getOutputStream();
            mapper.writeValue(outputStream, result);
    
            outputStream.flush();
            outputStream.close();
        }
    }

    Security设置过滤器,注意用了handler处理器,就不要设置默认登录页

    @EnableWebSecurity
    @Configuration
    public class SecurityConfig {
    
    
        // 验证成功和失败处理器
        @Autowired
        SecurityAuthSuccessHandler successHandler;
        @Autowired
        SecurityAuthFailureHandler failureHandler;
    
        /**
         * 密码编码器
         * @return
         */
        @Bean
        public BCryptPasswordEncoder bCryptPasswordEncoder(){
            return new BCryptPasswordEncoder();
        }
    
        /**
         *
         * 之前的做法有在configure(AuthenticationManagerBuilder)中配置auth.userDetailsService(myDetailsService).passwordEncoder(bcry)
         * 在configure(HttpSecurity)中配置http.authorizeHttpRequests()认证
         * 现在同样使用HttpSecurity参数,HttpSecurity:具体的权限控制规则配置
         * @param http
         * @return
         * @throws Exception
         */
        @Bean
        public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
            return http
                    .authorizeHttpRequests(auth->{
    
                        // 设置url权限,注意所有权限的配置顺序
                        auth.requestMatchers("/home").permitAll();
                        // 验证码
                        auth.requestMatchers("/captcha/**").permitAll();
                        // 静态资源
                        auth.requestMatchers("/js/**").permitAll();
                        auth.requestMatchers("/home/l0").hasRole("USER");
                        auth.requestMatchers("/home/l1/**").hasRole("Dog");
                        auth.requestMatchers("/home/l2/**").hasRole("Cat");
                        auth.anyRequest().authenticated();
                    })
                    .formLogin(conf->{
                        // 自定义表单登录页
                        // https://docs.spring.io/spring-security/reference/servlet/authentication/passwords/form.html
                        conf.loginPage("/login");
                        // 表单登录请求
                        conf.loginProcessingUrl("/login");
                        // 登录成功处理器,取消defaultSuccessUrl默认登录成功页可以看到效果,如登录失败处理器类似
                        // conf.successHandler(authenticationSuccessHandler());
                        // 登录失败处理器,但此处不能在表单上方显示error信息
                        // conf.failureHandler(authenticationFailureHandler());
                        // 使用handler类
                        conf.successHandler(successHandler);
                        conf.failureHandler(failureHandler);
                        // 默认登录成功页,使用了handler,就不要使用默认登录页,否则handler不起作用
                        // conf.defaultSuccessUrl("/home");
                        // 登录相关请求不需要认证
                        conf.permitAll();
                    })
                    .logout(conf->{
                        // 登出请求
                        conf.logoutUrl("/logout");
                        conf.logoutSuccessUrl("/login");
                        conf.permitAll();
                    })
                    // 使用自定义过滤器,并且
                    .addFilterBefore(new VerificationFilter(), UsernamePasswordAuthenticationFilter.class)
                    // 使用自定义的userDetails认证过程,
                    // .userDetailsService(null)
                    .csrf(AbstractHttpConfigurer::disable)// 关闭跨站请求伪造保护功能
                    .build();
        }
    }

    html添加验证码

    html>
    
    <html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
    <head>
        <title>Welcometitle>
    
        <script type="text/javascript" src="/js/jquery-3.7.0.min.js">script>
        <script type="text/javascript">
            $(function (){
                $("#btnLogin").click(function () {
                    console.log("ajax")
                    alert("ajax")
                    var uname = $("#username").val();
                    var pwd = $("#password").val();
                    // 用户输入验证码
                    var textcode = $("#textcode").val();
    
                    $.ajax({
                        url:"/login",
                        type:"POST",
                        // async: false,
                        data:{
                            "username":uname,
                            "password":pwd,
                            "code":textcode
                        },
                        dataType:"json",
                        success:function(res) {
                            console.log(res)
                            alert(res.status +":"+res.msg)
                        }
                    })
                })
            })
    
            function changeCode(){
            	// 防止缓存
                var url = "/captcha/code?t=" + new Date();
                $("#imageCode").attr("src", url);
            }
        script>
    
    
    head>
    <body>
    <h1>Welcome Log Inh1>
    <div th:if="${param.error}">
        Invalid username and password.div>
    <div th:if="${param.logout}">
        You have been logged out.div>
    
    
    <form th:action="@{/login}" method="post">
        <div>
            <input type="text" name="username" placeholder="Username"/>
        div>
        <div>
            <input type="password" name="password" placeholder="Password"/>
        div>
        <input type="submit" value="Log in" />
    form>
    
    <br>
        <div>
            使用Ajax登录,json传递数据<br>
            用户名:<input type="text" id="username"><br>
     码:<input type="password" id="password"><br>
            验证码:<input type="text" id="textcode">
            <img src="/captcha/code" id="imageCode"/>
            <a href="javascript:void(0)" onclick="changeCode()">重新获取a><br>
            <button id="btnLogin">登录button><br>
        div>
    body>
    html>

    完整代码在https://github.com/say-hey/springboot-security-concise


    __EOF__

  • 本文作者: 蔚然丶丶
  • 本文链接: https://www.cnblogs.com/wei-ran/p/17672274.html
  • 关于博主: 评论和私信会在第一时间回复。或者直接私信我。
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。
  • 相关阅读:
    JDBC 的定义及产品组件
    citavi显示期刊分区及影响因子
    LVGL demo project for ESP32
    【二分图染色】ARC 165 C
    Redis实战——分布式锁
    VIO深蓝学院第五章部分作业解读
    Focal and Global Knowledge Distillation for Detectors
    Android 虚拟分区详解(一) 参考资料推荐
    使用 Spring Boot 设置 Hibernate Envers
    二零二三充能必读 | 1024程序员狂欢节 —— 掌握前沿技术,探索未知领域
  • 原文地址:https://www.cnblogs.com/wei-ran/p/17672274.html