zoukankan      html  css  js  c++  java
  • Spring Boot + Vue 前后端分离项目 -- 后端登录接口实现

    前言

    在 Spring Boot + Vue 前后端分离项目中,后端只提供接口,页面处理和跳转都由前端实现,前后端通过 json 传输数据。

    后端项目,搭建骨架,可以参考文章:使用 MybatisGenerator 根据数据库自动生成 model、mapper 接口和 mapper.xml

    接下来开始后端登录接口的实现。

    处理 User 用户类

    让 User 类实现接口 UserDetails,并重写其中的方法:

    public class User implements Serializable, UserDetails {
    
        ......
    
        @Override
        public boolean isAccountNonExpired() {
            return true; // 账户没有过期
        }
    
        @Override
        public boolean isAccountNonLocked() {
            return true; // 账户没有锁定
        }
    
        @Override
        public boolean isCredentialsNonExpired() {
            return true; // 密码没有过期
        }
    
        @Override
        public boolean isEnabled() {
            return enabled;  // 账户可以使用(需要删除 User 类自带的 getEnabled 方法,如果有的话)
        }
    
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            return null;  // 暂不设置
        }
    
        ......
    }
    

    这里的 User 类,就是整个项目存放用户信息的类,有用户名、密码之类的。

    UserService 配置

    在包 service 下,新建一个 UserService 类,实现接口 UserDetailsService:

    @Service
    public class UserService implements UserDetailsService {
    
        @Autowired
        UserMapper userMapper;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            User user = userMapper.loadUserByUsername(username);
            if (user == null){
                throw new UsernameNotFoundException("用户名不存在!");
            }
            return user;
        }
    }
    

    关键代码:

    User user = userMapper.loadUserByUsername(username);
    

    这里重写了 loadUserByUsername 方法,通过用户名加载用户信息。

    UserMapper 配置

    在接口 UserMapper 中,定义 loadUserByUsername 方法:

    public interface UserMapper {
    
        ......
        User loadUserByUsername(String username);
    }
    

    UserMapper.xml 配置

    在 UserMapper.xml 实现方法 loadUserByUsername ,只需要添加一个查询语句:

      ......
      <select id="loadUserByUsername" resultMap="BaseResultMap">
        select * from user where username=#{username};
      </select>
      ......
    

    编写 RespBean 类

    RespBean 类主要用于向前端返回数据,会在后端很多地方用到,比如 SpringSecurity 配置中就会用到:

    public class RespBean {
        private Integer status;
        private String msg;
        private Object obj;
    
        public static RespBean ok(String msg){
            return new RespBean(200, msg, null);
        }
    
        public static RespBean ok(String msg, Object obj){
            return new RespBean(200, msg, obj);
        }
    
        public static RespBean error(String msg){
            return new RespBean(500, msg, null);
        }
    
        public static RespBean error(String msg, Object obj){
            return new RespBean(500, msg, obj);
        }
    
        private RespBean() {
        }
    
        private RespBean(Integer status, String msg, Object obj) {
            this.status = status;
            this.msg = msg;
            this.obj = obj;
        }
    
        public Integer getStatus() {
            return status;
        }
    
        public void setStatus(Integer status) {
            this.status = status;
        }
    
        public String getMsg() {
            return msg;
        }
    
        public void setMsg(String msg) {
            this.msg = msg;
        }
    
        public Object getObj() {
            return obj;
        }
    
        public void setObj(Object obj) {
            this.obj = obj;
        }
    }
    

    注意一下,RespBean 类有两个构造函数,都是 private 修饰,表明其他的类就不能直接调用 RespBean 生成新的对象,这样,RespBean 类只有一个对象实例。

    这里定义了两个静态方法 ok 和 error ,返回值都是 RespBean 对象。

    Spring Security 配置

    一般在 Spring Boot + Vue 前后端分离项目中,都是采用 Spring Security 作访问和权限控制。

    关于 Spring Security 的权限控制暂且不谈,这里主要是登录接口的配置。

    新建一个包 config,再建一个配置类 SecurityConfig ,继承 WebSecurityConfigurerAdapter :

    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        UserService userService;
    
    //    如果密码采用 BCryptPasswordEncoder 加密,则取消注释
    //    @Bean
    //    PasswordEncoder passwordEncoder(){
    //        return new BCryptPasswordEncoder();
    //    }
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userService);
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests().anyRequest().authenticated()
                    .and()
                    .formLogin()
                    .usernameParameter("username")
                    .passwordParameter("password")
                    .loginProcessingUrl("/doLogin")
                    .loginPage("/login")
                    .successHandler(new AuthenticationSuccessHandler() {
                        @Override
                        public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
                            httpServletResponse.setContentType("application/json;charset=utf-8");
                            PrintWriter writer = httpServletResponse.getWriter();
                            User user = (User) authentication.getPrincipal();
                            user.setPassword(null);
                            RespBean ok = RespBean.ok("登录成功!", user);
                            String string = new ObjectMapper().writeValueAsString(ok);
                            writer.write(string);
                            writer.flush();
                            writer.close();
                        }
                    })
                    .failureHandler(new AuthenticationFailureHandler() {
                        @Override
                        public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
                            httpServletResponse.setContentType("application/json;charset=utf-8");
                            PrintWriter writer = httpServletResponse.getWriter();
                            RespBean respBean = RespBean.error("登录失败!");
                            if (httpServletResponse instanceof LockedException){
                                respBean.setMsg("账户被锁定,请联系管理员!");
                            }else if (httpServletResponse instanceof BadCredentialsException){
                                respBean.setMsg("用户名或密码输入错误!");
                            }else if (httpServletResponse instanceof DisabledException){
                                respBean.setMsg("账户被禁用,请联系管理员!");
                            }else if (httpServletResponse instanceof AccountExpiredException){
                                respBean.setMsg("账户过期,请联系管理员!");
                            }else if (httpServletResponse instanceof CredentialsExpiredException){
                                respBean.setMsg("密码过期,请联系管理员!");
                            }else {
                                respBean.setMsg("登录失败!");
                            }
                            String string = new ObjectMapper().writeValueAsString(respBean);
                            writer.write(string);
                            writer.flush();
                            writer.close();
                        }
                    })
                    .permitAll()
                    .and()
                    .logout()
                    .logoutSuccessHandler(new LogoutSuccessHandler() {
                        @Override
                        public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
                            httpServletResponse.setContentType("application/json;charset=utf-8");
                            PrintWriter writer = httpServletResponse.getWriter();
                            writer.write(new ObjectMapper().writeValueAsString(RespBean.ok("注销登录")));
                            writer.flush();
                            writer.close();
                        }
                    })
                    .permitAll()
                    .and()
                    .csrf().disable();
        }
    }
    

    这个配置类比较长,我们挨个分析。

    由于我这个数据库中 User 类的密码采用的是 md5DigestAsHex 加密,所以没有用 SpringSecurity 自带的加密方式 BCryptPasswordEncoder。

    1、方法 configure(AuthenticationManagerBuilder auth) 解释

    代码如下:

        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userService);
        }
    

    这个方法身份验证,将登录的用户信息放在 auth 中,通过 userDetailsService 方法设置,传入的参数就是之前配置好的 userService。

    2、方法 configure(HttpSecurity http) 解释

    这个方法用于处理登录表单,主要分为三大块:

    • 登录成功的配置

    • 登录失败的配置

    • 注销登录的配置

    2.1 登录成功的配置

        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests().anyRequest().authenticated()
                    .and()
                    .formLogin()
                    .usernameParameter("username")
                    .passwordParameter("password")
                    .loginProcessingUrl("/doLogin")
                    .loginPage("/login")
                    .successHandler(new AuthenticationSuccessHandler() {
                        @Override
                        public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
                            httpServletResponse.setContentType("application/json;charset=utf-8");
                            PrintWriter writer = httpServletResponse.getWriter();
                            User user = (User) authentication.getPrincipal();
                            user.setPassword(null);
                            RespBean ok = RespBean.ok("登录成功!", user);
                            String string = new ObjectMapper().writeValueAsString(ok);
                            writer.write(string);
                            writer.flush();
                            writer.close();
                        }
                    })
                    ......
      }
    

    其中

    http.authorizeRequests().anyRequest().authenticated()
    

    表明所有请求都需要登录过后才能访问。

                    .formLogin()
                    .usernameParameter("username")
                    .passwordParameter("password")
                    .loginProcessingUrl("/doLogin")
                    .loginPage("/login")
    

    配置登录表的 用户名、密码、处理登录的页面、登录成功的页面。

    登录成功后:

                            httpServletResponse.setContentType("application/json;charset=utf-8");
                            PrintWriter writer = httpServletResponse.getWriter();
                            User user = (User) authentication.getPrincipal();
                            user.setPassword(null);
                            RespBean ok = RespBean.ok("登录成功!", user);
                            String string = new ObjectMapper().writeValueAsString(ok);
                            writer.write(string);
                            writer.flush();
                            writer.close();
    

    这些配置几乎都是固定的,直接 copy 即可。

    还有登录失败的配置、注销登录的配置,可以参考文章:Spring Security 基础教程 -- HttpSecurity 权限和登录表单配置

    配置密码加密的类

    由于我的数据库中 User 类的密码采用的是 md5DigestAsHex 加密,没有用 SpringSecurity 自带的加密方式 BCryptPasswordEncoder。

    所以需要自定义加密类,照样在 config 包下,新建 MyPasswordEncoder 类:

    @Component
    public class MyPasswordEncoder implements PasswordEncoder {
        @Override
        public String encode(CharSequence charSequence) {
            return DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());
        }
    
        @Override
        public boolean matches(CharSequence charSequence, String s) {
            return s.equals(DigestUtils.md5DigestAsHex(charSequence.toString().getBytes()));
        }
    }
    

    配置登录成功后的页面

    由于 SpringSecurity 在登录成功后,会自动跳转页面,而在 Spring Boot + Vue 前后端分离项目中,跳转页面是前端的事,后端只返回 json 数据即可。

    所以需要额外配置一下。

    在 controller 包下新建类 LoginController :

    @RestController
    public class LoginController {
        @GetMapping("/login")
        public RespBean login(){
            return RespBean.error("尚未登录,请登录!");
        }
    }
    

    测试登录接口

    一切准备就绪,需要测试一下效果。

    在 controller 包下新建类 HelloController:

    @RestController
    public class HelloController {
    
        @GetMapping("/hello")
        public String hello(){
            return "hello";
        }
    }
    
    

    这里定义了一个访问接口 /hello,最后用 postman 测试。

    postman 测试

    1. 启动项目,在 postman访问 http://localhost:8081/hello,效果如下:

    1. 登录操作

    1. 再访问接口 /hello

    1. 注销登录

    注销登录是 get 请求,默认接口时 logout :

    每天学习一点点,每天进步一点点。

  • 相关阅读:
    UML 入门课程
    在Visio中建立数据库模型的步骤
    phpMyAdmin
    采用软件负载均衡器实现web服务器集群
    Javascript 调用后台方法
    log4net 使用相关要点汇总
    静栈/动堆
    国外web 2.0网站模板
    yum应用学习笔记
    分页 : 存储分页 :row_number
  • 原文地址:https://www.cnblogs.com/youcoding/p/14729173.html
Copyright © 2011-2022 走看看