• 基于传统Session的登录


    前言:

    本人的一些简历上要回答的点。所以再此整理。

    亮点:

    使用Filter过滤器进行未登录状态自动跳转到登录页面的拦截,实现统一的权限管理。

    1 登陆功能

    1.1实体类和结果类

    前端页面

    约定 res.data.code为1时是登录成功。

    数据库的employee员工表:

    员工表存储员工的用户名、密码、身份证等信息,用来后台页面登录。

    这里password是加密后的数据,真实数据是123456

    创建实体类Employee

    1. @Data
    2.  
    3. //实体类实现Serializable接口,把对象转换为字节序列。序列化操作用于存储时,一般是对于NoSql数据库。
    4. public class Employee implements Serializable {
    5. //在反序列化的过程中,如果接收方为对象加载了一个类,如果该对象的serialVersionUID与对应持久化时的类不同,那么反序列化的过程中将会导致InvalidClassException异常。
    6.     private static final long serialVersionUID = 1L;
    7.     @JsonFormat(shape = JsonFormat.Shape.STRING)
    8.     private Long id;
    9.  
    10.     private String username;
    11.  
    12.     private String name;
    13.  
    14.     private String password;
    15.  
    16.     private String phone;
    17.  
    18.     private String sex;
    19.  
    20.     private String idNumber;
    21.  
    22.     private Integer status;
    23.     @TableField(fill=FieldFill.INSERT)
    24.     private LocalDateTime createTime;
    25.     @TableField(fill =FieldFill.INSERT_UPDATE)
    26.     private LocalDateTime updateTime;
    27. //阿里巴巴的开发规范中推荐每个表都带有一个createTime 和一个 updateTime, 但是每次自己手动添加太麻烦了,可以配置MP让其自动添加,使用@TableField的fill注解。
    28.     @TableField(fill = FieldFill.INSERT)
    29.     @JsonFormat(shape = JsonFormat.Shape.STRING)
    30.     private Long createUser;
    31.  
    32.     @TableField(fill = FieldFill.INSERT_UPDATE)
    33.     @JsonFormat(shape = JsonFormat.Shape.STRING)
    34.     private Long updateUser;
    35.  
    36. }

    R结果类:
     

    1. ​​​​​​​//用泛型格式,如果controller返回的是页面数据,则return R.success(page);如果返回的是成功消息,则return R.success("success");如果返回错误消息,return R.error("错误原因");
    2. @Data
    3. public class R {
    4.  
    5.     private Integer code; //编码:1成功,0和其它数字为失败
    6.  
    7.     private String msg; //错误信息
    8.  
    9.     private T data; //数据
    10.  
    11.     private Map map = new HashMap(); //动态数据
    12.     //静态类,controller返回时直接return R.success(T);
    13.     public static R success(T object) {
    14.         R r = new R();
    15.         r.data = object;
    16.         r.code = 1;
    17.         return r;
    18.     }
    19.  
    20.     public static R error(String msg) {
    21.         R r = new R();
    22.         r.msg = msg;
    23.         r.code = 0;
    24.         return r;
    25.     }
    26.     //添加动态数据
    27.     public R add(String key, Object value) {
    28.         this.map.put(key, value);
    29.         return this;
    30.     }
    31. }

    1.2 dao,service,controller

    dao层:

    1. @Mapper
    2. public interface EmployeeDao extends BaseMapper {
    3. }

    service层:
     

    1. public interface EmployeeService extends IService {
    2. }
    3. @Service
    4. public class EmployeeServiceImpl extends ServiceImpl implements EmployeeService {
    5. }

    这里业务接口继承了IService,业务实现类继承了ServiceImpl,这样service就继承了Mybatis-plus的各方法、变量。

    ServiceImpl类各方法(未过期)的作用

    getBaseMapper() getEntityClass() saveBatch() saveOrUpdate() saveOrUpdateBatch() updateBatchById() getOne() getMap() getObj() ServiceImpl类各属性的作用

    log:打印日志 baseMapper:实现了许多的SQL操作 entityClass:实体类 mapperClass:映射类
     

    controller层:

    @Slf4j
    @RestController
    @RequestMapping("/employee")
    public class EmployeeController {
     
        @Autowired
        private EmployeeService employeeService;
     
        /**
         * 员工登录
         * @param request
         * @param employee
         * @return
         */
        @PostMapping("/login")
        public R login(HttpServletRequest request,@RequestBody Employee employee){
     
            //1、将页面提交的密码password进行md5加密处理。同一个数据多次md5加密的结果是一样的,所以md5不能解密,但可以通过碰撞解密。
            String password = employee.getPassword();
            password = DigestUtils.md5DigestAsHex(password.getBytes());
     
            //2、根据页面提交的用户名username查询数据库
            LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(Employee::getUsername,employee.getUsername());
            Employee emp = employeeService.getOne(queryWrapper);
     
            //3、如果没有查询到则返回登录失败结果
            if(emp == null){
                return R.error("用户名或密码错误");
            }
     
            //4、密码比对,如果不一致则返回登录失败结果
            if(!emp.getPassword().equals(password)){
                return R.error("登录失败");
            }
     
            //5、查看员工状态,如果为已禁用状态,则返回员工已禁用结果
            if(emp.getStatus() == 0){
                return R.error("账号已被禁用");
            }
     
            //6、登录成功,将员工id存入Session并返回登录成功结果
            //被忘了存Session,默认有效期30分钟
            request.getSession().setAttribute("employee",emp.getId());
            return R.success(emp);
        }
     
        /**
         * 员工退出
         * @param request
         * @return
         */
        @PostMapping("/logout")
        public R logout(HttpServletRequest request){
            //清理Session中保存的当前登录员工的id
            request.getSession().removeAttribute("employee");
            return R.success("退出成功");
        }
    }

    1. @Slf4j
    2. @RestController
    3. @RequestMapping("/employee")
    4. public class EmployeeController {
    5. @Autowired
    6. private EmployeeService employeeService;
    7. /**
    8. * 员工登录
    9. * @param request
    10. * @param employee
    11. * @return
    12. */
    13. @PostMapping("/login")
    14. public R login(HttpServletRequest request,@RequestBody Employee employee){
    15. //1、将页面提交的密码password进行md5加密处理。同一个数据多次md5加密的结果是一样的,所以md5不能解密,但可以通过碰撞解密。
    16. String password = employee.getPassword();
    17. password = DigestUtils.md5DigestAsHex(password.getBytes());
    18. //2、根据页面提交的用户名username查询数据库
    19. LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
    20. queryWrapper.eq(Employee::getUsername,employee.getUsername());
    21. Employee emp = employeeService.getOne(queryWrapper);
    22. //3、如果没有查询到则返回登录失败结果
    23. if(emp == null){
    24. return R.error("用户名或密码错误");
    25. }
    26. //4、密码比对,如果不一致则返回登录失败结果
    27. if(!emp.getPassword().equals(password)){
    28. return R.error("登录失败");
    29. }
    30. //5、查看员工状态,如果为已禁用状态,则返回员工已禁用结果
    31. if(emp.getStatus() == 0){
    32. return R.error("账号已被禁用");
    33. }
    34. //6、登录成功,将员工id存入Session并返回登录成功结果
    35. //被忘了存Session,默认有效期30分钟
    36. request.getSession().setAttribute("employee",emp.getId());
    37. return R.success(emp);
    38. }
    39. /**
    40. * 员工退出
    41. * @param request
    42. * @return
    43. */
    44. @PostMapping("/logout")
    45. public R logout(HttpServletRequest request){
    46. //清理Session中保存的当前登录员工的id
    47. request.getSession().removeAttribute("employee");
    48. return R.success("退出成功");
    49. }
    50. }

    MD5信息摘要算法(英语:MD5 Message-Digest Algorithm),一种被广泛使用的密码散列函数,可以产生出一个128位(16字节)的散列值(hash value),用于确保信息传输完整一致。

    md5是一种不可逆的加密,一定记住是不可逆的。即得到密文无法还原明文。

    同一个数据多次md5加密的结果是一样的,所以md5不能解密,但可以通过碰撞解密。

    比如你得到一个md5加密串"E10ADC3949BA59ABBE56E057F20F883E",你有N个密码,通过md5加密加密N个密码,得到其中一个和"E10ADC3949BA59ABBE56E057F20F883E"一致,那么则密码一致。

    md5解密网站(实际是靠碰撞解密):md5在线解密破解,md5解密加密
     

    登出功能:删除Session

    1. @RequestMapping("/logout")
    2. public R logout(HttpServletRequest request){
    3. //尝试删除
    4. try {
    5. request.getSession().removeAttribute("employee");
    6. }catch (Exception e){
    7. //删除失败
    8. return R.error("登出失败");
    9. }
    10. return R.success("登出成功");
    11. }

    可以看到点击退出后就清除存储信息了。

    1.3 拦截页面登陆(添加过滤器,未登录状态自动跳转登录页面)

    这里的话用户直接url+资源名可以随便访问,所以要加个拦截器或者过滤器,没有登陆时,不给访问,自动跳转到登陆页面。

    过滤器和拦截器回顾

    拦截器和过滤器之间的区别:

    归属不同:Filter属于Servlet技术,依赖于Servlet容器;Interceptor属于SpringMVC技术,不依赖于servlet容器。 拦截内容不同:Filter对所有访问进行增强,几乎对所有请求起作用;Interceptor仅针对SpringMVC的访问进行增强,只能对action请求起作用。 调用次数不同:在action的生命周期中,过滤器只能在容器初始化时被调用一次,而拦截器可以多次被调用。 获取bean的权限不同:过滤器不能获取IOC容器中的各个bean;拦截器就可以,因为拦截器本身是个bean。这点很重要,在拦截器里注入一个service,可以调用业务逻辑。 底层机制不同:过滤器是基于函数回调,拦截器是基于java的反射机制的。


     

    代码实现:

    1.在引导类注解**@ServletComponentScan**

    2.在filter包下编写过滤器

    1. //坑点,路径是"/*",别忘了*
    2. @WebFilter(filterName = "loginCheckFilter",urlPatterns = "/*")
    3. @Slf4j
    4. public class LoginCheckFilter implements Filter{
    5. //路径匹配器,支持通配符,别忘了
    6. public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
    7. @Override
    8. public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    9. //0.要先把请求响应ServletXxx转成它的子接口HttpServletXxx,从而多了一些针对于Http协议的方法
    10. HttpServletRequest request = (HttpServletRequest) servletRequest;
    11. HttpServletResponse response = (HttpServletResponse) servletResponse;
    12. //1、获取本次请求的URI
    13. String requestURI = request.getRequestURI();// /backend/index.html
    14. log.info("拦截到请求:{}",requestURI);
    15. //定义不需要处理的请求路径,前端页面可以放行,只是除登录登出的后端拦截就行。
    16. String[] urls = new String[]{
    17. "/employee/login",
    18. "/employee/logout",
    19. "/backend/**",
    20. "/front/**"
    21. };
    22. //2、判断本次请求是否需要处理
    23. boolean check = check(urls, requestURI);
    24. //3、如果不需要处理,则直接放行
    25. if(check){
    26. log.info("本次请求{}不需要处理",requestURI);
    27. filterChain.doFilter(request,response);
    28. return;
    29. }
    30. //4、判断登录状态,如果已登录,则直接放行
    31. if(request.getSession().getAttribute("employee") != null){
    32. log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("employee"));
    33. filterChain.doFilter(request,response);
    34. return;
    35. }
    36. log.info("用户未登录");
    37. //5、如果未登录则返回未登录结果,通过输出流方式向客户端页面响应数据
    38. //必须错误信息NOTLOGIN,因为前端根据msg==“NOTLOGIN”和code==0判断未登录
    39. response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
    40. return;
    41. }
    42. /**
    43. * 路径匹配,检查本次请求是否需要放行
    44. * @param urls
    45. * @param requestURI
    46. * @return
    47. */
    48. public boolean check(String[] urls,String requestURI){
    49. for (String url : urls) {
    50. //这里是坑点,不能用equals
    51. boolean match = PATH_MATCHER.match(url, requestURI);
    52. if(match){
    53. return true;
    54. }
    55. }
    56. return false;
    57. }
    58. }

    坑点:

    • 0.别忘了引导类注解@ServletComponentScan
    • 1.通配符/*,别忘了*号
    • 2.别忘了创建静态final对象AntPathMatcher ,用它的match()方法进行url匹配,不能用equals

    响应到前端的是否登录信息将在requeset.js中处理:

    其实所有前端页面都引用了js/request.js里的前端拦截器,前端拦截器完成跳转到登陆页面,不在后端做处理

  • 相关阅读:
    Spring 面试题(注解、数据访问、AOP、MVC)
    6、行内元素和块元素
    Vue指令
    Netty P1 NIO 基础,网络编程
    Java项目:SSM物流快递管理系统
    js基础笔记学习318练习1
    车牌号识别(低级版)
    基于servlet+jsp+mysql网上书店系统
    Runable和Callable的区别?首先要搞清楚Thread以及FutureTask!
    方法论系列:用四个金字塔来说明金字塔原理
  • 原文地址:https://blog.csdn.net/qq_64948664/article/details/134483133