创建工程

本案例工程使用maven进行构建,使用SpringMVC、Servlet3.0实现。

创建maven工程 security-springmvc,工程结构如下:

引入如下依赖如下,注意:
1、由于是web工程,packaging设置为war
2、使用tomcat7-maven-plugin插件来运行工程

<?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.cyj</groupId><artifactId>security-springmvc</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.16.14</version></dependency></dependencies><build><finalName>security-springmvc</finalName><pluginManagement><plugins><plugin><groupId>org.apache.tomcat.maven</groupId><artifactId>tomcat7-maven-plugin</artifactId><version>2.2</version></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>

Spring 容器配置

在config包下定义ApplicationConfig.java,它对应web.xml中ContextLoaderListener的配置

package com.cyj.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;/*** @program: Spring-Security-OAuth2* @Description:* @Author C_Y_J* @create: 2021-01-28 13:08**/
@Configuration
@ComponentScan(basePackages = "com.cyj.security.springmvc",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.cyj.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.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;/*** @program: Spring-Security-OAuth2* @Description:* @Author C_Y_J* @create: 2021-01-28 13:11**/
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.cyj.security.springmvc",includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class)})
public class WebConfig implements WebMvcConfigurer {/*** 视频解析器** @return*/@Beanpublic InternalResourceViewResolver viewResolver() {InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();viewResolver.setPrefix("/WEB-INF/view/");viewResolver.setSuffix(".jsp");return viewResolver;}}

加载 Spring容器

在init包下定义Spring容器初始化类SpringApplicationInitializer,此类实现WebApplicationInitializer接口,Spring容器启动时加载WebApplicationInitializer接口的所有实现类。

package com.cyj.security.springmvc.init;import com.cyj.security.springmvc.config.ApplicationConfig;
import com.cyj.security.springmvc.config.WebConfig;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;/*** @program: Spring-Security-OAuth2* @Description:* @Author C_Y_J* @create: 2021-01-28 13:19**/
public class SpringApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {/*** spring容器,相当于加载 applicationContext.xml** @return*/@Overrideprotected Class<?>[] getRootConfigClasses() {//指定rootContext的配置类return new Class<?>[]{ApplicationConfig.class};}/*** servletContext,相当于加载springmvc.xml** @return*/@Overrideprotected Class<?>[] getServletConfigClasses() {//指定servletContext的配置类return new Class<?>[]{WebConfig.class};}/*** url-mapping** @return*/@Overrideprotected String[] getServletMappings() {return new String[]{"/"};}
}

SpringApplicationInitializer相当于web.xml,使用了servlet3.0开发则不需要再定义web.xml,
ApplicationConfig.class对应以下配置的application-context.xml,WebConfig.class对应以下配置的spring-mvc.xml,web.xml的内容参考:

<?xml version="1.0" encoding="UTF-8"?><web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><context-param><param-name>contextConfigLocation</param-name><param-value>/WEB-INF/application-context.xml</param-value></context-param><servlet><servlet-name>springmvc</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><init-param><param-name>contextConfigLocation</param-name><param-value>/WEB-INF/spring-mvc.xml</param-value></init-param><load-on-startup>1</load-on-startup></servlet><servlet-mapping><servlet-name>springmvc</servlet-name><url-pattern>/</url-pattern></servlet-mapping></web-app>

实现认证功能

认证页面

在webapp/WEB-INF/views下定义认证页面login.jsp,本案例只是测试认证流程,页面没有添加css样式,页面实现可填入用户名,密码,触发登录将提交表单信息至/login,内容如下:

<%@ 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>密&nbsp;&nbsp;&nbsp;码:<input type="password" name="password"><br><input type="submit" value="登录">
</form>
</body>
</html>

在WebConfig中新增如下配置,将/直接导向login.jsp页面:

    @Overridepublic void addViewControllers(ViewControllerRegistry registry) {registry.addViewController("/").setViewName("login");}

启动项目,访问/路径地址,进行测试


认证接口

用户进入认证页面,输入账号和密码,点击登录,请求/login进行身份认证。
(1)定义认证接口,此接口用于对传来的用户名、密码校验,若成功则返回该用户的详细信息,否则抛出错误异常:

package com.cyj.security.springmvc.service;import com.cyj.security.springmvc.pojo.AuthenticationRequest;
import com.cyj.security.springmvc.pojo.UserDto;/*** @program: Spring-Security-OAuth2* @Description:* @Author C_Y_J* @create: 2021-01-28 13:40**/
public interface AuthenticationService {/*** 用户认证** @param authenticationRequest* @return*/UserDto authentication(AuthenticationRequest authenticationRequest);
}

创建UserDto和AuthenticationRequest 实体类

package com.cyj.security.springmvc.pojo;import lombok.Data;/*** @program: Spring-Security-OAuth2* @Description:* @Author C_Y_J* @create: 2021-01-28 13:42**/
@Data
public class AuthenticationRequest {/*** 用户名*/private String username;/*** 密码*/private String password;
}
package com.cyj.security.springmvc.pojo;import lombok.AllArgsConstructor;
import lombok.Data;import java.util.Set;/*** @program: Spring-Security-OAuth2* @Description:* @Author C_Y_J* @create: 2021-01-28 13:42**/
@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;
}

(2)认证实现类,根据用户名查找用户信息,并校验密码,这里模拟了两个用户:

package com.cyj.security.springmvc.service.impl;import com.cyj.security.springmvc.pojo.AuthenticationRequest;
import com.cyj.security.springmvc.pojo.UserDto;
import com.cyj.security.springmvc.service.AuthenticationService;
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;/*** @program: Spring-Security-OAuth2* @Description:* @Author C_Y_J* @create: 2021-01-28 13:46**/
@Service
public class AuthenticationServiceImpl implements AuthenticationService {/*** 用户认证,校验用户身份信息是否合法** @param authenticationRequest* @return*/@Overridepublic 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;}/*** 根据账号查询用户信息** @param userName* @return*/private UserDto getUserDto(String userName) {return userMap.get(userName);}/*** 用户信息*/private Map<String, UserDto> userMap = new HashMap<>();{Set<String> authorities1 = new HashSet<>();//这个p1我们人为让它和/r/r1对应authorities1.add("p1");Set<String> authorities2 = new HashSet<>();//这个p2我们人为让它和/r/r2对应authorities2.add("p2");userMap.put("zhangsan", new UserDto("1010", "zhangsan", "123", "张三", "133443", authorities1));userMap.put("lisi", new UserDto("1011", "lisi", "456", "李四", "144553", authorities2));}
}

(3)登录Controller,对/login请求处理,调用AuthenticationService完成认证并返回登录结果提示信息:

package com.cyj.security.springmvc.controller;import com.cyj.security.springmvc.pojo.AuthenticationRequest;
import com.cyj.security.springmvc.pojo.UserDto;
import com.cyj.security.springmvc.service.AuthenticationService;
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.HttpSession;/*** @program: Spring-Security-OAuth2* @Description:* @Author C_Y_J* @create: 2021-01-28 13:50**/
@RestController
public class LoginController {@AutowiredAuthenticationService authenticationService;@RequestMapping(value = "/login", produces = "text/plain;charset=utf-8")public String login(AuthenticationRequest authenticationRequest, HttpSession session) {UserDto userDto = authenticationService.authentication(authenticationRequest);return userDto.getUsername() + "登录成功";}
}

(4)测试
填入正确的用户信息,页面提示登录成功:

以上的测试全部符合预期,到目前为止最基础的认证功能已经完成,它仅仅实现了对用户身份凭证的校验,若某用
户认证成功,只能说明他是该系统的一个合法用户,仅此而已。

实现会话功能

会话是指用户登入系统后,系统会记住该用户的登录状态,他可以在系统连续操作直到退出系统的过程。认证的目的是对系统资源的保护,每次对资源的访问,系统必须得知道是谁在访问资源,才能对该请求进行合法性拦截。因此,在认证成功后,一般会把认证成功的用户信息放入Session中,在后续的请求中,系统能够从Session中获取到当前用户,用这样的方式来实现会话机制。

(1)增加会话控制
修改LoginController,认证成功后,将用户信息放入当前会话。并增加用户登出方法,登出时将session置为失效。

    @RequestMapping(value = "/login", produces = "text/plain;charset=utf-8")public String login(AuthenticationRequest authenticationRequest, HttpSession session) {UserDto userDto = authenticationService.authentication(authenticationRequest);//存入sessionsession.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 "退出成功";}

(2)增加测试资源
修改LoginController,增加测试资源,它从当前会话session中获取当前登录用户,并返回提示信息给前台。

    @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 + " 访问资源r1";}

(3)测试
未登录情况下直接访问测试资源/r/r1:

成功登录的情况下访问测试资源/r/r1:

测试结果说明,在用户登录成功时,该用户信息已被成功放入session,并且后续请求可以正常从session中获取当前登录用户信息,符合预期结果。

实现授权功能

现在我们已经完成了用户身份凭证的校验以及登录的状态保持,并且我们也知道了如何获取当前登录用户(从Session中获取)的信息,接下来,用户访问系统需要经过授权,即需要完成如下功能:

匿名用户(未登录用户)访问拦截:禁止匿名用户访问某些资源。
登录用户访问拦截:根据用户的权限决定是否能访问某些资源。

(1)增加测试资源
我们想实现针对不同的用户能访问不同的资源,前提是得有多个资源,因此在LoginController中增加测试资源r2。
(2)实现授权拦截器
在interceptor包下定义SimpleAuthenticationInterceptor拦截器,实现授权拦截:
1、校验用户是否登录
2、校验用户是否拥有操作权限

package com.cyj.security.springmvc.interceptor;import com.cyj.security.springmvc.pojo.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;/*** @program: Spring-Security-OAuth2* @Description:* @Author C_Y_J* @create: 2021-01-28 17:18**/
@Component
public class SimpleAuthenticationInterceptor implements HandlerInterceptor {/*** 请求拦截方法** @param request* @param response* @param handler* @return* @throws Exception*/@Overridepublic 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, "请登录");} else {UserDto userDto = (UserDto) object;//请求的urlString requestURI = request.getRequestURI();if (userDto.getAuthorities().contains("p1") && requestURI.contains("/r/r1")) {return true;}if (userDto.getAuthorities().contains("p2") && requestURI.contains("/r/r2")) {return true;}writeContent(response, "没有权限,拒绝访问");}return false;}/*** 响应信息给客户端** @param response* @param msg* @throws IOException*/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();}}

在WebConfig中配置拦截器,匹配/r/**的资源为受保护的系统资源,访问该资源的请求进入
SimpleAuthenticationInterceptor拦截器。

    @Autowiredprivate SimpleAuthenticationInterceptor simpleAuthenticationInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(simpleAuthenticationInterceptor).addPathPatterns("/r/**");}

(3)测试
未登录情况下,/r/r1与/r/r2均提示 “请先登录”。
张三登录情况下,由于张三有p1权限,因此可以访问/r/r1,张三没有p2权限,访问/r/r2时提示 “权限不足 “。
李四登录情况下,由于李四有p2权限,因此可以访问/r/r2,李四没有p1权限,访问/r/r1时提示 “权限不足 “。
测试结果全部符合预期结果。

小结

基于Session的认证方式是一种常见的认证方式,至今还有非常多的系统在使用。我们在此小节使用Spring mvc技术对它进行简单实现,旨在让大家更清晰实在的了解用户认证、授权以及会话的功能意义及实现套路,也就是它们分别干了哪些事儿?大概需要怎么做?

而在正式生产项目中,我们往往会考虑使用第三方安全框架(如 spring security,shiro等安全框架)来实现认证授权功能,因为这样做能一定程度提高生产力,提高软件标准化程度,另外往往这些框架的可扩展性考虑的非常全面。但是缺点也非常明显,这些通用化组件为了提高支持范围会增加很多可能我们不需要的功能,结构上也会比较抽象,如果我们不够了解它,一旦出现问题,将会很难定位。

原文链接

(一)基于Session的认证方式相关推荐

  1. 分布式认证方案-基于session的认证方式

    在分布式的环境下,基于session的认证会出现一个问题,每个应用服务都需要在session中存储用户身份信息,通过负载均衡将本地的请求分配到另一个应用服务需要将session信息带过去,否则会重新认 ...

  2. 基于Session的认证方式_实现授权功能_Spring Security OAuth2.0认证授权---springcloud工作笔记118

    技术交流QQ群[JAVA,C++,Python,.NET,BigData,AI]:170933152 我们来实现基本的,session的授权功能,很简单实际上就是利用了springmvc的拦截器.不多 ...

  3. 基于Session的认证方式_实现会话功能_Spring Security OAuth2.0认证授权---springcloud工作笔记117

    技术交流QQ群[JAVA,C++,Python,.NET,BigData,AI]:170933152 这个很简单 我们就是把用户登录的信息保存到session中去可以看到 可以在session中保存一 ...

  4. 基于Session的认证方式_实现认证功能_Spring Security OAuth2.0认证授权---springcloud工作笔记116

    技术交流QQ群[JAVA,C++,Python,.NET,BigData,AI]:170933152 下面我会把代码和文档,帖上,下面看看实时过程.都很简单,主要说后面的分布式,认证授权和spring ...

  5. 基于Session的认证方式_创建工程_准备一个springmvc的工程_Spring Security OAuth2.0认证授权---springcloud工作笔记115

    技术交流QQ群[JAVA,C++,Python,.NET,BigData,AI]:170933152 这个以前都会这样用,但是现在基本上都是前后端分离了,这里为了演示才这样用的 pom.xml文件 & ...

  6. 基于Session的认证方式_认证流程_Spring Security OAuth2.0认证授权---springcloud工作笔记114

    技术交流QQ群[JAVA,C++,Python,.NET,BigData,AI]:170933152 开通了个人技术微信公众号:credream,有需要的朋友可以添加相互学习

  7. spring-security-学习笔记-02-基于Session的认证方式

    spring-security-学习笔记-02-基于Session的认证方式 文章目录 spring-security-学习笔记-02-基于Session的认证方式 2 基于Session的认证方式 ...

  8. 分布式认证方案-基于token的认证方式

    基于token的认证方式,服务端不用存储认证数据,易维护扩展性强, 客户端可以把token 存在任意地方,并且可以实现web和app统一认证机制.其缺点也很明显,token由于自包含信息,因此一般数据 ...

  9. 阐述Spring security实现用户认证授权的原理----基于session实现认证的方式

    一.认证流程 基于Session认证方式的流程是,用户认证成功后,在服务端生成用户相关的数据保存在session(当前会话),而发 给客户端 sesssion_id 存放到 cookie 中,这样用客 ...

最新文章

  1. 【MATLAB】二维矩阵可视化 MATLAB绘图
  2. Qt中的TCP客户端编程
  3. 网络库urillib3
  4. Hadoop RPC服务发布代码示例
  5. 利用原生js做数据管理平台
  6. php中取整的函数,利用PHP怎么对函数进行取整
  7. UITextfield键盘相关设置
  8. PHP注入漏洞(附代码,具体步骤)
  9. 容器技术Docker K8s 50 容器镜像服务(ACR)详解-使用与实践
  10. html怎么制作图片按钮效果,如何制作一个漂亮的 CSS 按钮
  11. 安卓逆向学习 之 KGB Messenger的writeup(2)
  12. 需要记住的的资料网址
  13. ip地址合不合法怎么看_怎样判断IP地址的非法性?
  14. iOS二十种超酷时尚艺术滤镜汇总
  15. 电影票房多视图可视化(echarts)
  16. idea2022.1版本创建maven项目没有src文件夹
  17. 基于FTP网盘系统设计
  18. 如何提问,提问技巧,如何有效的获取问题的答案
  19. codegear的希望
  20. 独家 | 首个5G远程手术成功,一文了解5G应用方向

热门文章

  1. estore电子商城-知识整合
  2. 单板计算机Beaglebone-Black首发上手体验
  3. 机器学习指南_机器学习-快速指南
  4. 计算机考研机试指南价格,计算机考研:机试指南(第2版)
  5. python语言实现【使用自带smtp服务的阿里云邮箱,发送邮件】
  6. Latex 添加参考文献引用及 Mac 编译可能遇到的问题:I couldn't open file name `bibfile.aux'
  7. Flink大数据实时计算系列-Flink窗口如何处理延时数据
  8. 哪些小学在普及python了_小学生在网吧用python抓取LOL英雄皮肤,步骤简单
  9. 企业机房冷通道监控解决方案
  10. 本科论文开题报告字体及格式规范是怎么样的?