SpringSecurity - SSM

SpringSecurity 对比 Shiro

SpringSecurity的特点:

  • 能和 Spring无缝贴合
  • 能实现全面的权限控制
  • 专门为 Web开发而设计
    • 旧版本不能脱离 Web环境使用
    • 新版本单独引入核心模块可以脱离 Web环境使用
  • 重量级

Shiro的特点:

  • 轻量级
  • 通用性
  • 但是在 Web环境下某些特定的需求需要手动编写代码定制

首先搭建 Web环境

引入 Web依赖

<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.20.RELEASE</version>
</dependency>
<!-- 引入 Servlet 容器中相关依赖 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<!-- JSP 页面使用的依赖 -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1.3-b06</version>
<scope>provided</scope>
</dependency>
</dependencies>

创建 SpringMVC的配置文件

<context:component-scan
base-package="com.atguigu.security"></context:component-scan>
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
<mvc:annotation-driven></mvc:annotation-driven>
<mvc:default-servlet-handler />

修改 web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0"> <servlet>
<servlet-name>springDispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springDispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>

逐步深入 SpringSecurity

加入 SpringSecurity的依赖

<!-- SpringSecurity 对 Web 应用进行权限管理 -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>4.2.10.RELEASE</version>
</dependency>
<!-- SpringSecurity 配置 -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>4.2.10.RELEASE</version>
</dependency>
<!-- SpringSecurity 标签库 -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
<version>4.2.10.RELEASE</version>
</dependency>

加入 SpringSecurity权限控制的 Filter

  • SpringSecurity 使用的是过滤器 Filter而不是拦截器 Interceptor

    • 意味着 SpringSecurity 能够管理的不仅仅是 SpringMVC 中的 controller请求,还包含 Web 应用中所有请求。
    • 比如:项目中的静态资源也会被拦截,从而进行权限控制。

修改 web.xml

<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

注意: <filter-name>springSecurityFilterChain</filter-name> 标 签 中 必 须 是 springSecurityFilterChain

加入 WebSecurity的配置类

@Configuration
@EnableWebSecurity // 开启 SpringSecurity关于 Web项目的注解支持
public class WebAppSecurityConfig extends WebSecurityConfigurerAdapter { }

实验 1:放行首页和静态资源

在配置类中重写父类的 configure(HttpSecurity security)方法

protected void configure(HttpSecurity security) throws Exception {
logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially
override subclass configure(HttpSecurity).");
security
.authorizeRequests()
.anyRequest().authenticated() //所有请求都需要进行认证
.and()
.formLogin()
.and()
.httpBasic();
}

重写后:

@Override
protected void configure(HttpSecurity security) throws Exception {
//super.configure(security); 注释掉将取消父类方法中的默认规则
security.authorizeRequests() //对请求进行授权
.antMatchers("/layui/**","/index.jsp") //使用 ANT 风格设置要授权的 URL 地址
.permitAll() //允许上面使用 ANT 风格设置的全部请求
.anyRequest() //其他未设置的全部请求
.authenticated(); //需要认证
}

实验 2:未认证请求跳转到登录页

@Override
protected void configure(HttpSecurity security) throws Exception {
//super.configure(security); 注释掉将取消父类方法中的默认规则
security.authorizeRequests() //对请求进行授权
.antMatchers("/layui/**","/index.jsp") //使用 ANT 风格设置要授权的 URL 地

.permitAll() //允许上面使用 ANT 风格设置的全部请求
.anyRequest() //其他未设置的全部请求
.authenticated() //需要认证
.and()
.formLogin() //设置未授权请求跳转到登录页面
.loginPage("/index.jsp") //指定登录页
.permitAll() //为登录页设置所有人都可以访问
// loginProcessingUrl()方法指定了登录地址,就会覆盖 loginPage()方法中设置的默认值 index.jsp POST
.loginProcessingUrl("/do/login.html") // 指定提交登录表单的地址
;
}

实验 3:设置登录系统的账号、密码

SpringSecurity 默认账号的请求参数名:username

SpringSecurity 默认密码的请求参数名:password

为了让 SpringSecurity接管用户登陆,要么修改页面上的表单项的 name 属性值,要么修改配置。

可以通过如下方法修改配置:

@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.usernameParameter("xxxName") // 定制登录账号的请求参数名
.passwordParameter("xxxPswd") // 定制登录密码的请求参数名
.defaultSuccessUrl("/main.html"); //设置登录成功后默认前往的 URL 地址
;
}

重写另外一个父类的方法,来设置登录系统的账号密码

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//super.configure(auth); 一定要禁用默认规则
auth
.inMemoryAuthentication()
.withUser("tom").password("123123") //设置账号密码
.roles("ADMIN") //设置角色
.and()
.withUser("jerry").password("456456")//设置另一个账号密码
.authorities("SAVE","EDIT"); //设置权限
}

Cannot pass a null GrantedAuthority collection 问 题 是 由 于 没 有 设 置 roles() 或 authorities()方法导致的

注意:登陆时需要携带参数 <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>

当然,也可以禁用 csrf

@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable() // 禁用 csrf
;
}

实验 4:用户注销

通过调用 HttpSecurity 对象的一系列方法设置注销功能。

  • logout()方法:开启注销功能
  • logoutUrl()方法:自定义注销功能的 URL 地址
  • logoutSuccessUrl()方法:退出成功后前往的 URL 地址
  • addLogoutHandler()方法:添加退出处理器
  • logoutSuccessHandler()方法:退出成功处理器

如果 CSRF 功能没有禁用,那么退出请求必须是 POST 方式。如果禁用了 CSRF功能则任何请求方式都可以。

实验 5:基于角色或权限进行访问控制

通过 HttpSecurity 对象设置资源的角色要求

protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests() //对请求进行授权
.antMatchers("/layui/**","/index.jsp") //使用 ANT 风格设置要授权的 URL 地址
.permitAll() //允许上面使用 ANT 风格设置的全部请求
.antMatchers("/level1/**")
.hasRole("学徒")
.antMatchers("/level2/**")
.hasRole(" 大师")
.antMatchers("/level3/**")
.hasRole(" 宗师")
.anyRequest() //其他未设置的全部请求
.authenticated() //需要认证
.and()
.formLogin() //设置未授权请求跳转到登录页面:开启表单登录功

.loginPage("/index.jsp") //指定登录页
.permitAll() //为登录页设置所有人都可以访问
.defaultSuccessUrl("/main.html") //设置登录成功后默认前往的 URL 地址
.and()
.logout()
.logoutUrl("/my/logout")
.logoutSuccessUrl("/index.jsp")
;
} protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("tom").password("123123") //设置账号密码
.roles("ADMIN","学徒","宗师") //设置角色
.and()
.withUser("jerry").password("456456")//设置另一个账号密码
.authorities("SAVE","EDIT"); //设置权限
}

类似异常的 catch顺序,路径的声明先后也很重要,大的路径声明在后面,小的在前面

实验 6 :自定义 403 错误页面

protected void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling()
.accessDeniedPage("/to/no/auth/page.html") // 当出现访问被拒绝时,指定跳转的页面
.accessDeniedHandler(new AccessDeniedHandler() { // 自定义的访问拒绝策略,开启该方法后,前一个方法就会失效了
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
httpServletRequest.setAttribute("message", "抱歉!您无法访问这个资源");
httpServletRequest.getRequestDispatcher("/WEB-INF/views/no_auth.jsp").forward(httpServletRequest, httpServletResponse);
}
})
;
}

实验 7 :记住我-内存版

登录表单携带名为 remember-me 的请求参数

如果不能使用 remember-me 作为请求参数名称,可以使用 rememberMeParameter()方法定制

protected void configure(HttpSecurity http) throws Exception {
http
.remeberMe(); // 开启记住我的功能
}

通过开发者工具看到浏览器端存储了名为remember-me的Cookie。根据这个Cookie 的 value 在服务器端找到以前登录的 User。

而且这个 Cookie 被设置为存储 2 个星期的时间。

实验 8 :记住我-数据库版

为了让服务器重启也不影响记住登录状态,将用户登录状态信息存入数据库。

需要在 WebAppSecurityConfig类中注入数据源

@Autowired
private DataSource dataSource;
@Configuration
@EnableWebSecurity // 启用 Web安全处理功能
public class WebAppSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired
private DataSource dataSource; protected void configure(HttpSecurity http) throws Exception {
JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
repository.setDataSource(dataSource); http
.rememberMe()
.tokenRepository(tokenRepository);
}
}

注意:需要进入 JdbcTokenRepositoryImpl 类中找到创建 persistent_logins 表的 SQL 语句创建persistent_logins 表。

CREATE TABLE persistent_logins (
username VARCHAR (64) NOT NULL,
series VARCHAR (64) PRIMARY KEY,
token VARCHAR (64) NOT NULL,
last_used TIMESTAMP NOT NULL
);

实验 9:查询数据库完成认证

创建存储用户的表

create table t_admin (
id int not null auto_increment,
loginacct varchar(255) not null,
userpswd char(32) not null,
username varchar(255) not null,
email varchar(255) not null,
createtime char(19),
primary key (id)
);

需要实现 UserDetailsService接口,重写 loadUserByUsername()方法加载用户

package com.atguigu.security.service;

/**
* @author :OliQ
* @date :Created on 2021/8/27 15:50
*/
@Service
public class MyUserDetailService implements UserDetailsService { @Autowired
private JdbcTemplate jdbcTemplate; @Override
public UserDetails loadUserByUsername(
// 表单提交的用户名
String s) throws UsernameNotFoundException { String sql = "SELECT id, loginacct, userpswd, username, email, createtime FROM t_admin WHERE loginacct = ?"; // 执行 sql语句,获取查询结果
Map<String, Object> resultMap = jdbcTemplate.queryForMap(sql, s); // 获取用户名、密码等数据
String loginacct = resultMap.get("loginacct").toString();
String userpswd = resultMap.get("userpswd").toString(); // 创建权限列表,如果是角色的话就需要手动拼接 ROLE_前缀
List<GrantedAuthority> list = AuthorityUtils.createAuthorityList("ADMIN", "USER", "ROLE_学徒"); // 传入用户名和密码,创建 User对象并返回
org.springframework.security.core.userdetails.User user
= new org.springframework.security.core.userdetails.User(loginacct, userpswd, list);
return user;
}
}

WebSecurityConfig中使用自定义的 UserDetailsService完成登陆

@Configuration
@EnableWebSecurity // 启用 Web安全处理功能
public class WebAppSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired
private UserDetailsService userDetailsService; @Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth
.userDetailsService(userDetailsService)
;
}
}

在存储 GrantedAuthority的列表中,区分 RoleAuthority的唯一准则是 RoleRole_的前缀,所以在使用自定义的 UserDetailsService登录时,在给用户添加权限时,处理 Role时要手动添加前缀。而使用内存版的时候,不能添加前缀。

实验 10:应用自定义密码加密规则

首先得保证数据库中存储的用户密码就是加密了的

SpringSecurity默认使用 BCryptPasswordEncoder实现密码的加解密。

BCryptPasswordEncoder 在加密时通过加入随机 盐值让每一次的加密结果都不同。能够避免密码的明文被猜到。

而在对明文和密文进行比较时,BCryptPasswordEncoder 会在密文的固定位置取出盐值,重新进行加密。

使用方法也很简单,只需要在 WebAppSecurityConfig中添加配置即可

@Configuration
@EnableWebSecurity // 启用 Web安全处理功能
public class WebAppSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private BCryptPasswordEncoder passwordEncoder; @Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth
.userDetailsService(userDetailsService) // 使用从数据库读取出来的用户相关信息
.passwordEncoder(passwordEncoder) // 设置加密方式
;
}

实验11:在页面上显示用户昵称

在 JSP中导入标签库

<%@ taglib uri="http://www.springframework.org/security/tags" prefix="security" %>

通过标签获取已登录的用户信息

<security:authentication property="principal.originalAdmin.userName"/>

实验12:权限控制

通过配置类设置某一路径的访问权限

protected void configure(HttpSecurity http) throws Exception {
http
.antMatchers("/admin/get/page.html")// 针对分页显示 Admin 数据设定访问控制
.hasRole("经理") // 要求具备经理角色
}

通过注解设置权限

@PreAuthorize("hasRole(' 部长')")
@RequestMapping("/role/get/page/info.json")
public ResultEntity<PageInfo<Role>> getPageInfo(
@RequestParam(value="pageNum", defaultValue="1") Integer pageNum,
@RequestParam(value="pageSize", defaultValue="5") Integer pageSize,
@RequestParam(value="keyword", defaultValue="") String keyword
) {
PageInfo<Role> pageInfo = roleService.getPageInfo(pageNum, pageSize, keyword);
return ResultEntity.successWithData(pageInfo);
}

同时需要在 WebAppSecurityConfig类中添加注解

// 启用全局方法权限控制功能,并且设置 prePostEnabled = true。保证@PreAuthority、
// @PostAuthority、@PreFilter、@PostFilter 生效
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebAppSecurityConfig extends WebSecurityConfigurerAdapter { }

hasAuthority也是一样的,在注解中可以这么写:

@PreAuthorize("hasRole(' 部长') OR hasAuthority('xxx')")

类似的还有 @PostAuthorize()注解,先执行方法然后根据方法返回值判断是否具备权限

例如:查询一个 Admin 对象,在@PostAuthorize 注解中和当前登录的 Admin 对象进行比较,如果不一致,则判断为不能访问。

实现“只能查自己”效果。@PostAuthorize("returnObject.data.loginAcct == principal.username")

还有 @PreFilter注解,在方法执行前对传入的参数进行过滤。只能对集合类型的数据进行过滤。

@PostFilter注解,在方法执行后对方法返回值进行过滤。只能对集合类型的数据进行过滤。

实验13:目标 9:页面元素的权限控制

针对页面上的局部元素,根据访问控制规则进行控制

在 JSP页面中,在需要被控制的元素外使用如下标签包裹

<security:authorize access="hasRole('经理')">
<!-- 开始和结束标签之间是要进行权限控制的部分。检测当前用户是否有权限,有权限
就显示这里的内容,没有权限就不显示。 -->
……
</security:authorize>

SpringSecurity 在 SSM架构中的使用的更多相关文章

  1. 将SSM架构中原来关于springSecurity3.x版本的写法配迁移到SpringBoot2.0框架中出现的问题解决记

    迁移过程中关于这个安全框架的问题很麻烦,springBoot自带的stater中的版本是5.0,原来系统有通过实现"org.springframework.security.authenti ...

  2. 认证鉴权与API权限控制在微服务架构中的设计与实现(四)

    引言: 本文系<认证鉴权与API权限控制在微服务架构中的设计与实现>系列的完结篇,前面三篇已经将认证鉴权与API权限控制的流程和主要细节讲解完.本文比较长,对这个系列进行收尾,主要内容包括 ...

  3. SSM框架中的前后端分离

    认识前后端分离 在传统的web应用开发中,大多数的程序员会将浏览器作为前后端的分界线.将浏览器中为用户进行页面展示的部分称之为前端,而将运行在服务器,为前端提供业务逻辑和数据准备的所有代码统称为后端. ...

  4. 手把手带你撸一把springsecurity框架源码中的认证流程

    提springsecurity之前,不得不说一下另外一个轻量级的安全框架Shiro,在springboot未出世之前,Shiro可谓是颇有统一J2EE的安全领域的趋势. 有关shiro的技术点 1.s ...

  5. 分享一个CQRS/ES架构中基于写文件的EventStore的设计思路

    最近打算用C#实现一个基于文件的EventStore. 什么是EventStore 关于什么是EventStore,如果还不清楚的朋友可以去了解下CQRS/Event Sourcing这种架构,我博客 ...

  6. 大型 JavaScript 应用架构中的模式

    原文:Patterns For Large-Scale JavaScript Application Architecture by @Addy Osmani 今天我们要讨论大型 JavaScript ...

  7. [转]大型 JavaScript 应用架构中的模式

    目录 1.我是谁,以及我为什么写这个主题 2.可以用140个字概述这篇文章吗? 3.究竟什么是“大型”JavaScript应用程序? 4.让我们回顾一下当前的架构 5.想得长远一些 6.头脑风暴 7. ...

  8. 三层架构中bll层把datatable转换为实体model的理解

    看了很多人的项目,很多都是用到三层架构,其中BLL层中有一种将DataTable转换为实体的方法.一直没有明白为啥要这样做,今天特意去搜索了一下,如果没有答案我是准备提问,寻求解答了.还好找到一个相关 ...

  9. 谈谈三层架构中Model的作用

    Model又叫实体类,这个东西,大家可能觉得不好分层.包括我以前在内,是这样理解的:UI<-->Model<-->BLL<-->Model<-->DAL ...

随机推荐

  1. RSA算法概述

    RSA算法的概述(个人理解,欢迎纠正) RSA是一种基于公钥密码体制的优秀加密算法,1978年由美国(MIT)的李维斯特(Rivest).沙米尔(Shamir).艾德曼(Adleman)提的.RSA算 ...

  2. php和js的不定参

    function my_func() { $args = func_get_args(); print_r($args); } my_func('php','java','node.js'); jav ...

  3. linux 磁盘io利用率高,分析的正确姿势

    一.背景简介 作为一个DBA难免不了会遇到性能问题,那么我们遇到性能问题该如何进行排查呢?例如我们在高并发的业务下,出现业务响应慢,处理时间长我们又该如何入手进行排查,本片文章将分析io高的情况下如何 ...

  4. Unhandled Exception: MissingPluginException(No implementation found for method launch on channel)

    在添加依赖包时,可能会出现Unhandled Exception: MissingPluginException(No implementation found for method launch o ...

  5. 《吐血整理》进阶系列教程-拿捏Fiddler抓包教程(14)-Fiddler断点(breakpoints)实战,篡改或伪造数据

    1.简介 上一篇主要就讲解和分享Fiddler断点的理论和操作,今天宏哥就用具体例子,将上一篇中的理论知识实践一下.而且在实际测试过程中,有时候需要修改请求或响应数据,或者直接模拟服务器响应,此时可以 ...

  6. identity4 系列————持久化配置篇[五]

    前言 上面已经介绍了3个例子了,并且介绍了如何去使用identity. 但是在前面的例子中,我们使用的都是在内存中操作,那么正式上线可能需要持久到数据库中. 这里值得说明的是,并不一定一定要持久化到数 ...

  7. Java接口自动化测试框架系列(一)自动化测试框架

    一.什么是自动化测试 自动化测试是把以人为驱动的测试行为转化为机器执行的一种过程. 通常,在设计了测试用例并通过评审之后,由测试人员根据测试用例一步步执行测试,得到实际结果与期望结果的比较. 为了节省 ...

  8. 第七十篇:Vue组件的使用

    好家伙, 1.vue的组件化开发 1.1.什么是组件? 组件是对UI结构的复用, vue是一个支持组件化开发的前端框架, vue中规定:组件的后缀名是.vue 例如:App.vue文件本质上就是一个v ...

  9. ipad好伴侣

    https://museapp.com/ Muse是用于研究笔记,阅读,草图,屏幕截图和书签的空间画布.

  10. MFRC522学习笔记

    MFRC522主要特性 容量为8K位(bits)=1K字节(bytes)EEPROM 分为16个扇区,每个扇区为4块,每块16个字节,以块为存取单位 每个扇区有独立的一组密码及访问控制 每张卡有唯一序 ...