什么是会话
用户通过认证后,为了避免用户的每次操作都进行认证可将用户的信息保存在会话中,会话就是系统为了保持当前用户的登录状态所提供的的机制,常见的有基于session方式、基于token方式等。
基于session的认证方式如下图:
它的交互流程是,用户认证成功后,在服务端生成用户相关的数据保存在session中,发给客户端的session_id存放到cookie中,这样用户客户端请求时带上session_id就可以验证服务器是否存在session数据,当用户退出系统或session过期销毁时,客户端的session_id也就失效了.
基于token方式如下图:
它交互流程流程是,用户认证成功后,服务端生成一个token发送给客户端,客户端可以放到cookie或者localStorage等存储中,每次请求时带上token,服务端收到token通过验证后即可确认用户身份.
授权的数据模型
授权可简单理解为Who对What进行How操作,包括如下:
Who:即主题,主题一般为用户,也可以是程序,需要访问系统中的资源.
What:即资源,如系统菜单、页面、按钮、代码方法、系统商品信息、系统订单信息。系统菜单、页面、按钮、代码方法都是属于系统功能资源。
How:权限许可,规定了用户对资源的操作许可,权限离开资源没有意义,如用户查询权限、用户添加权限、某个代码方法的调用权限、编号为001的用户的修改权限等,通过权限可知用户对哪些资源都有哪些操作许可.
RBAC
基于角色的访问控制
RBAC基于角色的访问控制是按角色进行授权,比如:主体的角色为总经理可以查询企业运营报表,查询员工工资信息等,访问控制流程如下:
授权代码可表示为:
if(主体.hasRole("总经理角色id")) {
查询工资
}
基于资源的访问控制
RBAC基于资源的访问控制是按资源进行授权,比如:用户必须具有查询工资的权限才可以查询员工工资信息等:
授权代码可表示为:
if(主体.hasPermission("查询工资权限标识")) {
查询工资
}
基于session认证方式
创建工程
创建maven工程,并且在pom.xml文件加入以下依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.cehcloud</groupId>
<artifactId>security</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
</dependencies>
</project>
Spring容器配置
在config包下定义ApplicationConfig.java,它对应web.xml中ContextLoadListener的配置
package com.cehcloud.cehc.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
/**
* @author Lenovo
* @date 2020/8/18 21:25
*/
@Configuration
@ComponentScan(basePackages = "com.cehcloud.cehc"
,excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class)})
public class ApplicationConfig {
// 在此配置除了Controller的其它bean,比如:数据库、事务管理器、业务bean等
}
servletContext配置
本案例采用Servlet3.0无web.xml方式,config包下定义的WebConfig.java,它对应DispatcherServlet配置.
package com.cehcloud.cehc.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
/**
* @author Lenovo
* @date 2020/8/18 21:29
*/
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.cehcloud.cehc"
,includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION,value = Controller.class)})
public class WebConfig implements WebMvcConfigurer {
@Bean
public InternalResourceViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/view/");
viewResolver.setSuffix(".html");
return viewResolver;
}
}
加载Spring容器
在init包下定义Spring容器初始化类SpringApplicationInitializer,此类实现WebApplicationInitializer接口,Spring容器启动时加载WebApplicationInitializer接口的所有实现类.
package com.cehcloud.cehc.init;
import com.cehcloud.cehc.config.ApplicationConfig;
import com.cehcloud.cehc.config.WebConfig;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
/**
* @author Lenovo
* @date 2020/8/18 21:45
*/
public class SpringApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
/**
* Spring容器,相当于加载applicationContext.xml
* @return
*/
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{
ApplicationConfig.class};
}
/**
* servletContext,相当于加载springmvc.xml
* @return
*/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{
WebConfig.class};
}
/**
* url-mapping
* @return
*/
@Override
protected String[] getServletMappings() {
return new String[]{
"/"};
}
}
登录页面:
<%--
Created by IntelliJ IDEA.
User: Lenovo
Date: 2020/8/19
Time: 13:53
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录</title>
</head>
<body>
<form action="login" method="post">
用户名:<input type = "text" name = "username"/><br>
密码:<input type="password" name="password"/><br>
<input type="submit" value="登录">
</form>
</body>
</html>
在WebConfig中新增如下配置,将直接导入登录页面:
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("login");
}
认证接口
用户进入认证页面,输入账号和密码,点击登录,进行身份验证.
(1)定义认证接口,此接口用于对传来的用户名、密码校验。
package com.cehcloud.cehc.service;
/**
* @author Lenovo
* @date 2020/8/19 14:25
*/
import com.cehcloud.cehc.model.AuthenticationRequest;
import com.cehcloud.cehc.model.UserDTO;
/**
* 认证服务
*/
public interface AuthenticationService {
/**
* 用户认证
* @param authenticationRequest 用户认证请求
* @return 认证成功的用户信息
*/
UserDTO authentication(AuthenticationRequest authenticationRequest);
}
认证请求结构:
package com.cehcloud.cehc.model;
import lombok.Data;
/**
* @author Lenovo
* @date 2020/8/19 14:27
*/
@Data
public class AuthenticationRequest {
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
}
认证成功后返回用户详细信息,也就是当前登录用户的信息:
package com.cehcloud.cehc.model;
import lombok.Data;
/**
* @author Lenovo
* @date 2020/8/19 14:27
*/
@Data
@AllArgsConstructor
public class UserDTO {
private String id;
private String username;
private String password;
private String fullName;
private String mobile;
}
(2)认证实现类,根据用户名查找用户信息,并校验密码,这里模拟两个用户:
package com.cehcloud.cehc.service.impl;
import com.cehcloud.cehc.model.AuthenticationRequest;
import com.cehcloud.cehc.model.UserDTO;
import com.cehcloud.cehc.service.AuthenticationService;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.HashMap;
import java.util.Map;
/**
* @author Lenovo
* @date 2020/8/19 14:35
*/
@Service
public class AuthenticationServiceImpl implements AuthenticationService {
@Override
public UserDTO authentication(AuthenticationRequest authenticationRequest) {
if (authenticationRequest == null
|| StringUtils.isEmpty(authenticationRequest.getUsername())
|| StringUtils.isEmpty(authenticationRequest.getPassword())) {
throw new RuntimeException("账号或密码为空");
}
UserDTO userDTO = getUserDTO(authenticationRequest.getUsername());
if (userDTO == null) {
throw new RuntimeException("查不到该用户");
}
if (!authenticationRequest.getPassword().equals(userDTO.getPassword())) {
throw new RuntimeException("账号或者密码错误");
}
return userDTO;
}
/**
* 模拟用户查询
* @param username
* @return
*/
public UserDTO getUserDTO(String username) {
return userDTOMap.get(username);
}
/**
* 用户信息
*/
private Map<String, UserDTO> userDTOMap = new HashMap<>();
{
userDTOMap.put("zhangsan", new UserDTO());
userDTOMap.put("lisi", new UserDTO());
}
}
(3)登录controller,对登录请求处理,它调用AuthenticationService完成认证并返回登录提示信息.
package com.cehcloud.cehc.controller;
import com.cehcloud.cehc.model.AuthenticationRequest;
import com.cehcloud.cehc.model.UserDTO;
import com.cehcloud.cehc.service.AuthenticationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author Lenovo
* @date 2020/8/19 14:54
*/
@RestController
public class LoginController {
@Autowired
AuthenticationService authenticationService;
@RequestMapping(value = "/login", produces = "text/plain; charset=utf-8")
public String login(AuthenticationRequest authenticationRequest) {
UserDTO userDTO = authenticationService.authentication(authenticationRequest);
return userDTO.getUsername() + "登录成功";
}
}
实现会话功能
(1)增加会话功能
首先在UserDTO中定义一个SESSION_USER_KEY,作为Session中存放登录用户信息的key.
public static final String SESSION_USER_KEY = “_user” ;
然后修改LoginController,认证成功后,将用户信息放在当前会话.并增加用户登录方法,退出登录时session失效.
package com.cehcloud.cehc.controller;
import com.cehcloud.cehc.model.AuthenticationRequest;
import com.cehcloud.cehc.model.UserDTO;
import com.cehcloud.cehc.service.AuthenticationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpSession;
/**
* @author Lenovo
* @date 2020/8/19 14:54
*/
@RestController
public class LoginController {
@Autowired
AuthenticationService authenticationService;
@RequestMapping(value = "/login", produces = "text/plain; charset=utf-8")
public String login(AuthenticationRequest authenticationRequest, HttpSession session) {
UserDTO userDTO = authenticationService.authentication(authenticationRequest);
// 存入session
session.setAttribute(UserDTO.SESSION_USER_KEY, userDTO);
return userDTO.getUsername() + "登录成功";
}
@GetMapping(value = "/logout", produces = "text/plain; charset=utf-8")
public String logout(HttpSession session) {
session.invalidate();
return "退出成功";
}
}