用户认证登录流程
设计表格
教师和学生
如果添加权限角色是可以的,从教师表格查询登录用户设置为教师角色,从学生表格查询的用户设置为学生角色
t_student(role=student)
t_teacher(role=teacher)
t_roles角色表格
扥
登录流程
最终效果:如果访问后台(增删改查的功能),不能是学生角色,需要时教师角色,如果是学生账号登录,不允许操作后台班级,专业,学生,教师的任何操作,学生账号只能通过首页教学中心查看自己的学科课程
如果实现这个效果,认证中,第一次访问生成用户信息保存,后续访问,通过过滤器判断当前用户身份,如果是学生,只允许访问教学中心查询课程,如果是教师,都允许。
- 用户提供登录表单访问认证中心,传递用户名和密码
- 认证中心到数据库查询student ,teacher保存数据到redis拿到key值返回给客户端浏览器cookie。
- 客户端登录成功之后,继续访问目标功能,到了zuul网关
- 网关从请求中获取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版权协议,转载请附上原文出处链接和本声明。