2021-10-12-------用户认证登录系统

用户认证登录流程

设计表格

教师和学生
如果添加权限角色是可以的,从教师表格查询登录用户设置为教师角色,从学生表格查询的用户设置为学生角色
t_student(role=student)
t_teacher(role=teacher)
t_roles角色表格

登录流程
最终效果:如果访问后台(增删改查的功能),不能是学生角色,需要时教师角色,如果是学生账号登录,不允许操作后台班级,专业,学生,教师的任何操作,学生账号只能通过首页教学中心查看自己的学科课程
如果实现这个效果,认证中,第一次访问生成用户信息保存,后续访问,通过过滤器判断当前用户身份,如果是学生,只允许访问教学中心查询课程,如果是教师,都允许。
在这里插入图片描述

  1. 用户提供登录表单访问认证中心,传递用户名和密码
  2. 认证中心到数据库查询student ,teacher保存数据到redis拿到key值返回给客户端浏览器cookie。
  3. 客户端登录成功之后,继续访问目标功能,到了zuul网关
  4. 网关从请求中获取cookie,使用key值到redis检查是否存在认证对象,从而返回应有的结果
    • 拒绝访问,没有权限角色
    • 允许访问,通过路由找到后端的微服务

实现认证登录

准备一个可用的redis

单节点,哨兵,redis-cluster
在这里插入图片描述

创建一个认证中心系统

  • maven
  • pom继承
<!--持久层相关-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>
        <!--redis相关-->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>
  • 属性配置application.properties
server.port=8000
#数据源
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.password=root
spring.datasource.username=root
spring.datasource.url=jdbc:mysql://localhost:3306/aise?useSSL=false
#mybatis
mybatis.mapper-locations=classpath:/mappers/*.xml
mybatis.configuration.map-underscore-to-camel-case=true
#redis相关 单节点2个属性
spring.redis.host=192.168.184.160
spring.redis.port=9000
  • 启动类
@SpringBootApplication
@MapperScan("cn.tedu.auth.mapper")

项目中缺少的内容

  • redisTemplate自定义配置(重新定义序列化key和value类型),key是String序列化,value是java的序列化
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;

/**
 * 重新自定义RedisTemplate对象。重新定义序列化
 */
@Configuration
public class RedisConfig {
    @Bean("redisTemplate")
    public RedisTemplate initRedisTemplate(RedisConnectionFactory factory){
        //重新定义RedisTemplate
        RedisTemplate<String,Object> redisTemplate=new RedisTemplate<String,Object>();
        //重新定义序列化
        redisTemplate.setKeySerializer(RedisSerializer.string());//key值的序列化类型,默认java的object,定义成string
        redisTemplate.setValueSerializer(RedisSerializer.java());
        //设置链接工厂
        redisTemplate.setConnectionFactory(factory);
        return redisTemplate;
    }
}
  • authentication认证用户记录队列
    String account(学生或者教师账号)
    String role(学生或者教师角色)
    Long id(学生或者教师id值)
/**
 * 这个类,在认证登录结束后,要保存登录者信息
 * 所以要存储到redis,并且在网关中要读取
 * 进行序列化操作,实现序列化接口
 */
public class Authentication implements Serializable {
    public static final String STUDENT="student";
    public static final String TEACHER="teacher";
    private String account;
    private Long id;
    private String role;
    public String getAccount() {
        return account;
    }
    public void setAccount(String account) {
        this.account = account;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getRole() {
        return role;
    }
    public void setRole(String role) {
        this.role = role;
    }
}

认证登录接口功能

  • 接口文件
    在这里插入图片描述
  • AuthController
import cn.tedu.aise.pojo.vo.SystemResult;
import cn.tedu.auth.service.AuthService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
 * 控制登录认证逻辑
 */
@RestController
@RequestMapping("/tedu/aise/v1.0/auth")
public class AuthController {
    /**
     * /login地址
     * username password
     * SystemSesult
     */
    @Autowired
    private AuthService authService;
    @RequestMapping(value="/login",produces = "application/json;charset=utf-8")
    public SystemResult doLogin(String username, String password,
                                HttpServletRequest request, HttpServletResponse response) throws Exception {
        /*
            1.调用业务层代码,实现登录逻辑 SystemResult status就是控制层定义的200 300 其他,msg消息
            data存放redis的key值
            2.COOKIE中只要没有抛异常 200 300都要将SystemResult中的data存放到cookie里 定义一个cookie的名字
            AISE_TICKET;将数据返回。
         */
        SystemResult result=authService.doLogin(username,password);
        String ticket= (String) result.getData();
        Cookie cookie=new Cookie("AISE_TICKET",ticket);
        response.addCookie(cookie);
        return result;
    }
}

SystemResult .java

/**
 * 包名有含义的,javabean有如下几种常见的用来命令封装数据的
 * 类额
 * vo:视图对象
 * dto:封装表单对象
 * entity:实体对象
 * domain:域对象
 */

/**
 * systemResult是和ajax交互的一个数据封装类
 * status
 * msg
 * data
 */
public class SystemResult {
    private Integer status;
    private String msg;
    private Object data;
    //这里我们为了调用这个类方便,多额外开发一些工具功能的方法
    //无参构造
    public SystemResult(){ }
    //有参构造
    public SystemResult(Integer status, String msg, Object data) {
        this.status = status;
        this.msg = msg;
        this.data = data;
    }
    //实现两个工具方法 ok build
    public static SystemResult ok(){
        //一旦调用,将会返回一个固定status msg data数值的对象
        return new SystemResult(200,"ok",null);
        //{"status":200,"msg":"ok","data":null} 常用这个方法生成一个成功信息的返回值
    }
    public static SystemResult build(Integer status,String msg,Object data){
        return new SystemResult(status,msg,data);
    }

    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 getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }
}

在这里插入图片描述

public interface AuthService {
    SystemResult doLogin(String username, String password) throws Exception;
}

AuthServiceImpl.java

import cn.tedu.aise.ex.MyException;
import cn.tedu.aise.pojo.entity.StudentEntity;
import cn.tedu.aise.pojo.entity.TeacherEntity;
import cn.tedu.aise.pojo.vo.SystemResult;
import cn.tedu.aise.utils.MD5Util;
import cn.tedu.aise.utils.SaltUtis;
import cn.tedu.auth.mapper.AuthMapper;
import cn.tedu.auth.pojo.Authentication;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.UUID;
@Service
public class AuthServiceImpl implements AuthService{
    @Autowired
    private AuthMapper authMapper;
    @Autowired
    private RedisTemplate redisTemplate;
    @Override
    public SystemResult doLogin(String username, String password) throws Exception {
        /*
            1.使用username查询数据库,查询不到 跑出201 找不到用户异常
            2.使用密码password明文,加密对比数据库密文,对比不成功 密码不正确异常
            3.根据查询的表格 封装Authentication对象,生成key值存储redis
            4.封装SystemResult对象,200 300 status ok的msg key的data
         */
        //select * from t_student where student_account=#{username}
        //select * from t_teacher where teacher_account=#{username}
        TeacherEntity teacherEntity=authMapper.selectTeacherByAccount(username);
        StudentEntity studentEntity=authMapper.selectStudentByAccount(username);
        Authentication authentication=new Authentication();
        if(studentEntity==null&&teacherEntity==null){
            throw new MyException("账号不存在");
        }
        if(teacherEntity==null&&studentEntity!=null){
            //密码对比
            matches(studentEntity.getPassword(),password);
            //最终角色student
            authentication.setAccount(username);
            authentication.setId(studentEntity.getId());
            authentication.setRole(Authentication.STUDENT);
        }
        if(teacherEntity!=null&&studentEntity==null){
            //密码对比
            matches(teacherEntity.getPassword(),password);
            //最终角色是teacher
            authentication.setAccount(username);
            authentication.setId(teacherEntity.getId());
            authentication.setRole(Authentication.TEACHER);
        }
        if(teacherEntity!=null&&studentEntity!=null){
            throw new MyException("账号在老师和学生重复不能使用,请联系管理员");
        }
        //存储redis key值,value值(authentcation对象)
        //key值在认证登录时,设计特点,应该是每个用户key值不同,同一个用户不同登录时间key值不同 uuid完全符合要求
        String ticket= UUID.randomUUID().toString();
        redisTemplate.boundValueOps(ticket).set(authentication);//set ticket authentication(做了序列化)
        //根据authentication的角色,返回200或者300
        return "student".equals(authentication.getRole())?
                SystemResult.build(200,"学生登录成功",ticket):
                SystemResult.build(300,"老师登陆成功",ticket);
    }
    //对比密码的公用方法
    public void matches(String encodedPass,String password) throws Exception{
        //对手动输入这个明文做加密
        String md5Password= MD5Util.md5(password+ SaltUtis.SALT);
        //判断非空
        if(StringUtils.isEmpty(encodedPass)||StringUtils.isEmpty(md5Password)){
            throw new MyException("数据库密码或者输入密码为空");
        }
        if(!(encodedPass.equals(md5Password))){
            throw new MyException("您输入的密码有误");
        }
    }
}

在这里插入图片描述

import cn.tedu.aise.pojo.entity.StudentEntity;
import cn.tedu.aise.pojo.entity.TeacherEntity;
import org.apache.ibatis.annotations.Param;
public interface AuthMapper {
    TeacherEntity selectTeacherByAccount(@Param("username")String username);
    StudentEntity selectStudentByAccount(@Param("username")String username);
}

在这里插入图片描述

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.tedu.auth.mapper.AuthMapper">
    <!--查询学生-->
    <select id="selectStudentByAccount" resultType="cn.tedu.aise.pojo.entity.StudentEntity">
        select * from t_student where student_account=#{username};
    </select>
    <!--查询教师-->
    <select id="selectTeacherByAccount" resultType="cn.tedu.aise.pojo.entity.TeacherEntity">
        select * from t_teacher where teacher_account=#{username};
    </select>
</mapper>

启动程度单独验证

redis需要开启
在这里插入图片描述

前端页面和登录页面nginx配置

将登录页面传到ftp login.html
nginx配置login的接口localtion
zuul网关过滤器,检查cookie值,让学生只能访问教学中心查询课程,教师可以访问所有

2.5网关过滤逻辑

由于使用redis读取登录注册存储数据,需要在网关中添加redis配置逻辑redisConfig,依赖,属性,拷贝Authentication对象
过滤器的逻辑

  • 拿到cookid中的key值,到redis获取authentication对象
  • 判断什么样的请求需要拦截,什么不需要拦截

网关添加一个权限校验过滤器
AuthorizationFilter .java

import cn.tedu.auth.pojo.Authentication;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
 * 当前是教务系统的唯一的一个权限校验的过滤器
 *
 */
@Component
public class AuthorizationFilter extends ZuulFilter{
    @Override
    public String filterType() {
        return "pre";
    }
    @Override
    public int filterOrder() {
        return 0;
    }
    /**
     * 判断当前请求是否要进行认证授权的逻辑校验
     * 有没有登陆过,有没有访问该资源的权限
     * @return
     */
    @Override
    public boolean shouldFilter() {
        return true;
    }
    @Autowired
    private RedisTemplate redisTemplate;
    /**
     * 当前教务系统的核心过滤逻辑
     * @return
     * @throws ZuulException
     */
    @Override
    public Object run() throws ZuulException {
        /* 失败返回json{"status":"201","msg":"您没有权限",null}
         1.拿到当前请求对象和响应对象
         2.从请求拿到cookie AISE_TICKET 读取redis中的数据
         3.判断是否登录认证
         4.通过地址 /zuul-course/tedu/aise/v1.0/course/manage/user/bind 两个角色都行,其他都是老师角色
         5.如果是允许通过的情况,我们要从authentication中拿到id值,向后传递一个请求的域属性
         */
        RequestContext currentContext = RequestContext.getCurrentContext();//拿到请求上下文对象,包含着请求和响应
        HttpServletRequest request = currentContext.getRequest();
        HttpServletResponse response = currentContext.getResponse();//
        //设置返回json类型,头contentType
        response.setContentType("application/json;charset=utf-8");
        //从request中拿到cookie
        String ticket=getCookieValue("AISE_TICKET",request);
        //从request拿到请求路径
        String uriPath=request.getRequestURI();
        if(!StringUtils.isEmpty(ticket)){
            //读取redis
            Authentication authentication = (Authentication) redisTemplate.boundValueOps(ticket).get();
            if(authentication!=null){
                //登录状态没问题,判断权限,拿到角色
                String role=authentication.getRole();//student teacher
                if(!("/zuul-course/tedu/aise/v1.0/course/manage/user/bind".equals(uriPath))){
                    //当前请求路径不是这个地址,角色必须是teacher不能是student
                    if("student".equals(role)){
                        //不可以通过,应该拦截
                        String responseJson="{\"status\":201,\"msg\":\"学生权限不能访问后台\",\"data\":null}";
                        currentContext.setSendZuulResponse(false);//向后调用微服务,是否允许
                        currentContext.setResponseBody(responseJson);
                        //currentContext.setResponseStatusCode(403);//401没登录认证,403登录认证了,但是没权限
                    }
                }else{
                    //说明当前请求是 教学中心查看当前用户学生,用户教师绑定课程的地址 需要在域属性中添加2个属性值
                    //一个是id 一个的身份student teacher
                    request.setAttribute("id",authentication.getId());
                    request.setAttribute("role",authentication.getRole());
                    currentContext.setRequest(request);

                }
            }else{
                //ticket是空
                String responseJson="{\"status\":201,\"msg\":\"你登录,但是登录状态消失了\",\"data\":null}";
                currentContext.setSendZuulResponse(false);//向后调用微服务,是否允许
                currentContext.setResponseBody(responseJson);
                //currentContext.setResponseStatusCode(401);//401没登录认证,403登录认证了,但是没权限
            }
        }else{
            //ticket是空
            String responseJson="{\"status\":201,\"msg\":\"你没有登录\",\"data\":null}";
            currentContext.setSendZuulResponse(false);//向后调用微服务,是否允许
            currentContext.setResponseBody(responseJson);
            //currentContext.setResponseStatusCode(401);//401没登录认证,403登录认证了,但是没权限
        }
        return null;//返回值无用,只要没有使用currentContext拦截请求,请求就正常向后发送
    }
    public String getCookieValue(String cookieName,HttpServletRequest request){
        Cookie[] cookies = request.getCookies();
        if(cookies==null){
            return "";
        }
        String value=null;
        for (Cookie cookie : cookies) {
            String name = cookie.getName();
            if(name!=null&&name.equals(cookieName)){
                value=cookie.getValue();
            }
        }
        return value;
    }
}

登录页面login,html

配置nginx.conf

页面请求到达认证中心
在这里插入图片描述

登录认证的完整流程
在这里插入图片描述


版权声明:本文为qq_44494957原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。