什么是认证
进入移动互联网时代,大家每天都在刷手机,常用的软件有微信、支付宝、头条等,下边拿微信来举例子说明认证相关的基本概念,在初次使用微信前需要注册成为微信用户,然后输入账号和密码即可登录微信,输入账号和密码登录微信的过程就是认证。
系统为什么要认证?
认证是为了保护系统的隐私数据与资源,用户的身份合法方可访问该系统的资源。
认证 :用户认证就是判断一个用户的身份是否合法的过程,用户去访问系统资源时系统要求验证用户的身份信息,身份合法方可继续访问,不合法则拒绝访问。常见的用户身份认证方式有:用户名密码登录,二维码登录,手机短信登录,指纹认证等方式。
什么是会话
用户认证通过后,为了避免用户的每次操作都进行认证可将用户的信息保证在会话中。会话就是系统为了保持当前用户的登录状态所提供的机制,常见的有基于session方式、基于token方式等。
-
基于session的认证方式:
它的交互流程是,用户认证成功后,在服务端生成用户相关的数据保存在session(当前会话)中,发给客户端的sesssion_id 存放到 cookie中,这样用户客户端请求时带上 session_id 就可以验证服务器端是否存在 session 数据,以此完成用户的合法校验,当用户退出系统或session过期销毁时,客户端的session_id也就无效了 -
基于token方式:
它的交互流程是,用户认证成功后,服务端生成一个token发给客户端,客户端可以放到 cookie 或 localStorage等存储中,每次请求时带上 token,服务端收到token通过验证后即可确认用户身份。
基于session的认证方式由Servlet规范定制,服务端要存储session信息需要占用内存资源,客户端需要支持cookie;基于token的方式则一般不需要服务端存储token,并且不限制客户端的存储方式。如今移动互联网时代更多类型的客户端需要接入系统,系统多是采用前后端分离的架构进行实现,所以基于token的方式更适合。
什么是授权
还拿微信来举例子,微信登录成功后用户即可使用微信的功能,比如,发红包、发朋友圈、添加好友等,没有绑定银行卡的用户是无法发送红包的,绑定银行卡的用户才可以发红包,发红包功能、发朋友圈功能都是微信的资源即功能资源,用户拥有发红包功能的权限才可以正常使用发送红包功能,拥有发朋友圈功能的权限才可以使用发朋友圈功能,这个根据用户的权限来控制用户使用资源的过程就是授权。
为什么要授权?
认证是为了保证用户身份的合法性,授权则是为了更细粒度的对隐私数据进行划分,授权是在认证通过后发生的,控制不同的用户能够访问不同的资源。
授权: 授权是用户认证通过根据用户的权限来控制用户访问资源的过程,拥有资源的访问权限则正常访问,没有权限则拒绝访问。
如何实现授权?
基于角色的访问控制(不是特别推荐)
基于角色的访问控制(Role-Based Access Control)是按角色进行授权,比如:主体的角色为总经理可以查询企业运营报表,查询员工工资信息等,访问控制流程如下:
伪代码
if(主体.hasRole("总经理角色id")){
查询工资
}
如果上图中查询工资所需要的角色变化为总经理和部门经理,此时就需要修改判断逻辑为“判断用户的角色是否是总经理或部门经理”,修改代码如下:
if(主体.hasRole("总经理角色id") || 主体.hasRole("部门经理角色id")){
查询工资
}
根据上边的例子发现,当需要修改角色的权限时就需要修改授权的相关代码,系统可扩展性差。
基于资源的访问控制(推荐使用)
基于资源的访问控制(Resource-Based Access Control)是按资源(或权限)进行授权,比如:用户必须具有查询工资权限才可以查询员工工资信息等,访问控制流程如下:
if(主体.hasPermission("查询工资权限标识")){
查询工资
}
优点:系统设计时定义好查询工资的权限标识,即使查询工资所需要的角色变化为总经理和部门经理也不需要修改授权代码,系统可扩展性强。只需要给相应的角色权限就可以。
未使用spring security框架的认证
依赖
<?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>org.example</groupId>
<artifactId>spring_security_mvc</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
</dependencies>
</project>
不使用xml,使用配置类
Spring 容器配置
在config包下定义ApplicationConfig.java,它对应web.xml中ContextLoaderListener的配置
package com.hhy.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 HuangHaiyang
* @date 2020/08/17
* @description: description
* @version: 1.0.0
*/
@Configuration //相当于applicationContext.xml
@ComponentScan(basePackages = "com.hhy"
,excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = Controller.class)})
public class ApplicationConfig {
}
servletContext配置
本案例采用Servlet3.0无web.xml方式,的config包下定义WebConfig.java,它对应s对应于DispatcherServlet配置。
package com.hhy.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.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
/**
* @author HuangHaiyang
* @date 2020/08/17
* @description: description
* @version: 1.0.0
*/
@Configuration//就相当于springmvc.xml文件
@EnableWebMvc
@ComponentScan(basePackages = "com.hhy"
,includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = Controller.class)})
public class WebConfig implements WebMvcConfigurer {
//注入自定义拦截规则
@Autowired
private DiyAuthenticationInterceptor diyAuthenticationInterceptor;
@Bean
public InternalResourceViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/view/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("login");
}
//配置拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(diyAuthenticationInterceptor);
}
}
加载 Spring容器
定义Spring容器初始化类SpringApplicationInitializer,此类实现WebApplicationInitializer接口,Spring容器启动时加载WebApplicationInitializer接口的所有实现类。
package com.hhy.config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
/**
* @author Administrator
* @version 1.0
**/
public class SpringApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
//spring容器,相当于加载 applicationContext.xml
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{ApplicationConfig.class};
}
//servletContext,相当于加载springmvc.xml
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{WebConfig.class};
}
//url-mapping
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
实现授权拦截器
自定义DiyAuthenticationInterceptor 拦截器,实现授权拦截:
1、校验用户是否登录
2、校验用户是否拥有操作权限
package com.hhy.config;
import com.hhy.entity.UserDto;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* @author HuangHaiyang
* @date 2020/08/17
* @description: 实现授权拦截器
* @version: 1.0.0
*/
@Component
public class DiyAuthenticationInterceptor implements HandlerInterceptor {
//请求拦截方法
//1、校验用户是否登录
//2、校验用户是否拥有操作权限
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
UserDto user = (UserDto)request.getSession().getAttribute(UserDto.SESSION_USER_KEY);
if(user==null){
writeContent(response,"请登录");
}
String requestURI = request.getRequestURI();
//拥有p1权限可以访问r1
if(user.getAuthorities().contains("p1")&&requestURI.contains("/r1")){
return true;
}
//拥有p1权限可以访问r1
if(user.getAuthorities().contains("p2")&&requestURI.contains("/r2")){
return true;
}
//没有权限
writeContent(response,"没有权限,拒绝访问");
return false;
}
//响应输出
private void writeContent(HttpServletResponse response, String msg) throws IOException {
response.setContentType("text/html;charset=utf‐8");
PrintWriter writer = response.getWriter();
writer.print(msg);
writer.close();
response.resetBuffer();
}
}
public interface AuthenticationService {
/**
* 用户认证
* @param authenticationRequest 用户认证请求,账号和密码
* @return 认证成功的用户信息
*/
UserDto authentication(AuthenticationRequest authenticationRequest);
}
认证实现类,根据用户名查找用户信息,并校验密码,这里模拟了两个用户
package com.hhy.service;
import com.hhy.entity.AuthenticationRequest;
import com.hhy.entity.UserDto;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* @author HuangHaiyang
* @date 2020/08/17
* @description: description
* @version: 1.0.0
*/
@Service
public class AuthenticationServiceImpl implements AuthenticationService {
/**
* 用户认证,校验用户身份信息是否合法
*
* @param authenticationRequest 用户认证请求,账号和密码
* @return 认证成功的用户信息
*/
@Override
public UserDto authentication(AuthenticationRequest authenticationRequest) {
//校验参数是否为空
if(authenticationRequest == null
|| StringUtils.isEmpty(authenticationRequest.getUsername())
|| StringUtils.isEmpty(authenticationRequest.getPassword())){
throw new RuntimeException("账号和密码为空");
}
//根据账号去查询数据库,这里测试程序采用模拟方法
UserDto user = getUserDto(authenticationRequest.getUsername());
//判断用户是否为空
if(user == null){
throw new RuntimeException("查询不到该用户");
}
//校验密码
if(!authenticationRequest.getPassword().equals(user.getPassword())){
throw new RuntimeException("账号或密码错误");
}
//认证通过,返回用户身份信息
return user;
}
//根据账号查询用户信息
private UserDto getUserDto(String userName){
return userMap.get(userName);
}
//用户信息
private Map<String,UserDto> userMap = new HashMap<>();
{
Set<String> authorities1 = new HashSet<>();
authorities1.add("p1");//这个p1我们人为让它和/r/r1对应
Set<String> authorities2 = new HashSet<>();
authorities2.add("p2");//这个p2我们人为让它和/r/r2对应
userMap.put("zhangsan",new UserDto("1010","zhangsan","123","张三","133443",authorities1));
userMap.put("lisi",new UserDto("1011","lisi","456","李四","144553",authorities2));
}
}
package com.hhy.entity;
import lombok.Data;
/**
* @author HuangHaiyang
* @date 2020/08/17
* @description: description
* @version: 1.0.0
*/
@Data
public class AuthenticationRequest {
//认证请求参数,账号、密码。。
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
}
package com.hhy.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.Set;
/**
* @author HuangHaiyang
* @date 2020/08/17
* @description: description
* @version: 1.0.0
*/
@Data
@AllArgsConstructor
public class UserDto {
public static final String SESSION_USER_KEY = "_user";
//用户身份信息
private String id;
private String username;
private String password;
private String fullname;
private String mobile;
/**
* 用户权限
*/
private Set<String> authorities;
}
登录Controller,对/login请求处理,它调用AuthenticationService完成认证并返回登录结果提示信息:
package com.hhy.controller;
import com.hhy.entity.AuthenticationRequest;
import com.hhy.entity.UserDto;
import com.hhy.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 Administrator
* @version 1.0
**/
@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 "退出成功";
}
/**
* 测试资源1
* @param session
* @return
*/
@GetMapping(value = "/r/r1",produces = {"text/plain;charset=UTF‐8"})
public String r1(HttpSession session){
String fullname = null;
Object userObj = session.getAttribute(UserDto.SESSION_USER_KEY);
if(userObj != null){
fullname = ((UserDto)userObj).getFullname();
}else{
fullname = "匿名";
}
return fullname + " 访问资源1";
}
/**
* 测试资源2
* @param session
* @return
*/
@GetMapping(value = "/r/r2",produces = {"text/plain;charset=UTF‐8"})
public String r2(HttpSession session){
String fullname = null;
Object userObj = session.getAttribute(UserDto.SESSION_USER_KEY);
if(userObj != null){
fullname = ((UserDto)userObj).getFullname();
}else{
fullname = "匿名";
}
return fullname + " 访问资源2";
}
}
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="utf-8" %>
<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>