做认证除了oauth 和 shiro,我们这里学的是oauth2.0
1.1
认证和授权是解决软件系统安全的两个基础概念,认证是校验使用者的身份是否合法,授权是校验使用者是否拥有操作资源的权限。互联网的发展也促进了软件开放性设计的进步,软件既要开放又要安全,如何使用统一的方案解决软件自身及外部系统的认证需求是当前互联网应用要考虑的问题,OAuth2.0协议是当前开放流行的认证协议,本课程使用流行的Spring Security认证框架及OAuth2.0协议实现单体及分布式系统的认证授权技术解决方案。
1.2
什么是认证
什么是授权
1.3 什么是会话
用户认证通过后,为了避免用户的每次操作都需要认证,我们将用户的登录信息放在绘画中,会话就是系统为了保证用户登录页状态提供的机制,常见的有基于session和基于token方式
session和token的区别:
a. session需要将数据放到cookie中,token不需要可以放到cookie,也可以放到localStorage中
b. 基于session,服务器要存储session,而token服务器不需要存储,而是校验客户端发送的token是否正确
1.4授权的数据模型
授权可以理解为who对what(which) 进行how操作,即那些用户对什么样的资源,进行怎么的操作
1.5.1 如何进行授权:
2.基于session的认证方式:
下面实战: 首先我么你自己写 一个基于session的认证登录,来熟悉过程,然后在基于spring的security来进行认证授权
pom文件
<?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.xiaofeifei.security</groupId>
<artifactId>security-springmvc</artifactId>
<version>1.0-SNAPSHOT</version>
<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.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>5.1.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>5.1.4.RELEASE</version>
</dependency>
<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>
<build>
<finalName>securityspringmvc</finalName>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>9090</port>
<path>/</path>
<server>tomcat7</server>
<ignorePackaging>true</ignorePackaging>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<encoding>utf-8</encoding>
<useDefaultDelimiters>true</useDefaultDelimiters>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/*</include>
</includes>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
我们用的是servlet 3.0 所以很多配置都不在config中配置了,我们直接在java类中配置,还有web.xml我们都在java类中进行配置
基础知识:
spring容器指的就是 applicationContext.xml
servletContext 就是springmvc.xml
我们配置applicationContext.xml对应的类
package com.xiaofeifei.security.springmvc.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
@Configuration //相当于applicationContext.xml ,servlet3.0后,配置可以放到class中
@ComponentScan(basePackages = "com.xiaofeifei.security.springmvc"
,excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class)})
//在此配置除了Controller的其它bean,比如:数据库链接池、事务管理器、业务bean等。这里配置springcontext
//controller在servletcontext中会配置扫描
public class ApplicationConfig {
}
我们配置 springmvc.xml 对应的类
package com.xiaofeifei.security.springmvc.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;
@Configuration //这个配置相当于springmvc.xml
@EnableWebMvc
@ComponentScan(basePackages = "com.xiaofeifei.security.springmvc"
,includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = Controller.class)}) //这里只扫描有Controller注解的包,因为其他的包有ApplicationConfig包进行了扫描
public class WebConfig implements WebMvcConfigurer {
//配置视图解析器
@Bean
public InternalResourceViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/view/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
//将/ 直接导向login.jsp
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("login");
}
}
然后我们需要web.xml,实现ApplicationInitializer或者继承这个类的子类 ,作用是相当于在web.xml
package com.xiaofeifei.security.springmvc.init;
import com.xiaofeifei.security.springmvc.config.ApplicationConfig;
import com.xiaofeifei.security.springmvc.config.WebConfig;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
//继承的类的一个父接口有WebApplicationInitializer ,作用是相当于在web.xml中配置的servlet和listener
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}; // 配置servlet
}
//作用是加载url-mapping
@Override
protected String[] getServletMappings() {
return new String[]{"/"}; //配置访问路径,这里是根路径
}
}
创建jsp页面
然后点击运行:
启动后会报错,这个lombok 和tomcat7兼容性,但是不影响效果
定义controller
package com.xiaofeifei.security.springmvc.controller;
import com.xiaofeifei.security.springmvc.model.AuthenticationRequest;
import com.xiaofeifei.security.springmvc.model.UserDto;
import com.xiaofeifei.security.springmvc.service.impl.AuthenticationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
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;
@RestController
public class LoginController {
@Autowired
private AuthenticationService authenticationService;
@RequestMapping(value = "/login", produces = "text/plain;charset=utf-8") //text/plain;charset=utf-8 的作用是高速浏览器返回的是文本类型
public String login(AuthenticationRequest authenticationRequest, HttpSession session) {
UserDto userDto = authenticationService.authentication(authenticationRequest);
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 "退出成功";
}
@GetMapping(value = "r/r1", produces = "text/plain;charset=utf-8")
public String r1(HttpSession session) {
String fullname = null;
Object object = session.getAttribute(UserDto.SESSION_USER_KEY);
if (object == null) {
fullname = "匿名";
}else {
UserDto userDto = (UserDto) object;
fullname = userDto.getFullname();
}
return fullname + "访问资源r1";
}
@GetMapping(value = "r/r2", produces = "text/plain;charset=utf-8")
public String r2(HttpSession session) {
String fullname = null;
Object object = session.getAttribute(UserDto.SESSION_USER_KEY);
if (object == null) {
fullname = "匿名";
}else {
UserDto userDto = (UserDto) object;
fullname = userDto.getFullname();
}
return fullname + "访问资源r1";
}
}
定义拦截器
package com.xiaofeifei.security.springmvc.interceptor;
import com.xiaofeifei.security.springmvc.model.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;
@Component
public class SimpleAuthenticationInterceptor implements HandlerInterceptor {
@Override //在调用所有controller之前我们需要进行拦截
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//在这个方法中校验用户请求的url是否在用户的权限范围内
Object object = request.getSession().getAttribute(UserDto.SESSION_USER_KEY);
if (object == null) {
writeContent(response, "请登录");
}
UserDto userDto = (UserDto) object;
String requestURI = request.getRequestURI();
if (userDto.getAuthorities().contains("p1") && requestURI.contains("/r/r1")) //拥有p1 权限的可以访问 /r/r1
{
return true;
}
if (userDto.getAuthorities().contains("p2") && requestURI.contains("/r/r2")) // 拥有p2权限的可以访问 /r/r2
{
return true;
}
writeContent(response, "没有权限,拒绝访问");
return false;
}
//响应信息给客户端
private void writeContent(HttpServletResponse response, String message) throws IOException {
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.print(message);
writer.close();
}
}
定义连个实体类
package com.xiaofeifei.security.springmvc.model;
import lombok.Data;
/**
* 请求认证参数:账号和密码
*/
@Data //用lombok插件的Data注解,然后会在编译的时候生成get和set方法
public class AuthenticationRequest {
private String username;
private String password;
}
package com.xiaofeifei.security.springmvc.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.Set;
/**
* 用户信息插件
*/
@Data //用lombok插件的Data注解,然后会在编译的时候生成get和set方法
@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;
}
下面是基于security:
spring security 的原理
AccessDecesionManager 类有三个实现类,分别有不同的投票规则
利用spring security 出现403 问题解决方案
其中stateless的方式就是以token的方式,不需要用session的方式来进行认证授权
spring security 的退出
基于方法的拦截,我们一般对controller进行拦截,因为controller是面向用户造作的接口
分布式系统:
这个过程就是:首先客户端向资源拥有者 申请授权,得到授权(授权码)后,然后带着授权码去访问认证服务 申请令牌,认证服务判断授权码正确,然后返回令牌,然后客户端带着令牌去访问请求用户信息,