第7章 系统管理-权限设置

1. 权限控制、SpringSecurity入门及进阶

【目标】

了解认证和授权的概念

【路径】

1:认证和授权的概念

  • 认证:登录(用户名和密码)
  • 授权:访问系统功能的权限

2:权限模块的数据模型

  • 用户表
  • 角色表
  • 权限表
  • 菜单表

用户,角色是多对多

权限,角色是多对多

菜单,角色是多对多

1.1. 认证和授权概念【理解】

前面我们已经完成了健康后台管理系统(health_web)的部分功能,例如检查项管理、检查组管理、套餐管理、预约设置等。接下来我们需要思考2个问题:

问题1:在生产环境下我们如果不登录后台系统就可以完成这些功能操作吗?

答案显然是否定的,要操作这些功能必须首先登录到系统才可以。(用户登录系统–>认证)

问题2:是不是所有用户,只要登录成功就都可以操作所有功能呢?

答案是否定的,并不是所有的用户都可以操作这些功能。不同的用户可能拥有不同的权限,这就需要进行授权了。(用户登录之后,对每个用户进行授权,通过授权去访问系统中不同的功能–>授权)

认证:系统提供的用于识别用户身份的功能,通常提供用户名和密码进行登录其实就是在进行认证,认证的目的是让系统知道你是谁。

说白了:登陆系统了,就可以访问,没登陆就不能访问。验证用户名与密码是否存在数据库中(t_user)

授权:用户认证成功后,需要为用户授权,其实就是指定当前用户可以操作哪些功能。

​ 用户登陆后,查询获取用户所拥有的权限绑定到用户, 当进行权限验证时就可以取用与登陆用户绑定的权限与访问的资源标定的权限进行匹配

商店里的 矿泉水 3块钱 资源权限标定。标定好了需要访问这个资源时的权限

登陆用户 有多少钱?老婆给予的,老婆授权。

授权是为了做权限验证的。登陆用户身上所拥有的权限集合

/checkitem/findPage.do 查询检查项 只有健康管理师才可以访问

员工A 来登陆 (查看套餐的权限) 来访 查询检查项(/checkitem/findPage.do 健康管理师) 报没有权限

认证与授权的目的,更好的保护资源(使用后台管理功能)


本章节就是要对后台系统进行权限控制,其本质就是对用户进行认证和授权。

1.2. 权限模块数据模型

前面已经分析了认证和授权的概念,要实现最终的权限控制,需要有一套表结构支撑:

用户表t_user、权限表t_permission、角色表t_role、菜单表t_menu、用户角色关系表t_user_role、角色权限关系表t_role_permission、角色菜单关系表t_role_menu。

表之间关系如下图:

通过上图可以看到,权限模块共涉及到7张表。在这7张表中,角色表起到了至关重要的作用,其处于核心位置,我们把基于角色的权限控制叫做RBAC,因为用户、权限、菜单都和角色是多对多关系。

接下来我们可以分析一下在认证和授权过程中分别会使用到哪些表:

认证过程:只需要用户表就可以了,在用户登录时可以查询用户表t_user进行校验,判断用户输入的用户名和密码是否正确。

授权过程:用户必须完成认证之后才可以进行授权,首先可以根据用户查询其角色,再根据角色查询对应的菜单,这样就确定了用户能够看到哪些菜单。然后再根据用户的角色查询对应的权限,这样就确定了用户拥有哪些权限。所以授权过程会用到上面7张表。

【小结】

  1. 认证和授权

    • 认证: 提供账号和密码进行登录认证, 系统知道你的身份
    • 授权: 根据不同的身份, 赋予不同的权限,不同的权限,可访问系统不同的功能( 系统的功能也要标定访问权限)
  2. RBAC权限模块数据模型(基于角色的权限控制)

    • 用户表
    • 角色表
    • 权限表
    • 菜单表

    用户,角色是多对多 用户角色表

    权限,角色是多对多 角色权限表

    菜单,角色是多对多 角色菜单表

    一共7张表

1.3. Spring Security简介

【目标】

知道什么是Spring Security

【路径】

  1. Spring Security简介
  2. Spring Security使用需要的坐标

【讲解】

Spring Security是 Spring提供的安全认证服务的框架。 使用Spring Security可以帮助我们来简化认证和授权的过程。官网:https://spring.io/projects/spring-security

对应的maven坐标:

<dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-web</artifactId><version>5.0.5.RELEASE</version>
</dependency>
<dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-config</artifactId><version>5.0.5.RELEASE</version>
</dependency>

常用的权限框架除了Spring Security,还有Apache的shiro框架。

【小结】

  1. SpringSecurity是Spring家族的一个安全框架, 简化我们开发里面的认证和授权过程, (登陆, 访问url时的权限控制 secuirty帮我们做了)
  2. SpringSecurity内部封装了Filter(只需要在web.xml容器中配置一个过滤器–代理过滤器,真实的过滤器(12个)在spring的容器中配置)
  3. 常见的安全框架
    • Spring的 SpringSecurity 配置复杂 有个好老爸(spring) 无缝整合 链式模式
    • Apache的Shiro http://shiro.apache.org/ 比较简单 apache 写整合 外观者模式

1.4. Spring Security入门案例-【重点】

【目标】

【需求】

​ 使用Spring Security进行控制: 网站(一些页面)需要登录才能访问(认证)

【路径】

  1. 创建Maven health_parent的子工程 springsecurity_demo,导入坐标(依赖health_interface)
  2. 配置web.xml(前端控制器加载spring, SpringSecurity代理过滤器)
  3. 创建spring-security.xml(核心配置文件, spring要加载这个配置)

【讲解】

1.4.1. 工程搭建

创建maven工程,打包方式为war,为了方便起见我们可以让入门案例工程依赖health_interface,这样相关的依赖都继承过来了。

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.itheima</groupId><artifactId>springsecurity_demo</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>com.itheima</groupId><artifactId>health_interface</artifactId><version>1.0-SNAPSHOT</version></dependency></dependencies>
</project>

内置提供index.html页面,内容为“登录成功”!!

1.4.2. 配置web.xml

【路径】

1:DelegatingFilterProxy用于整合第三方框架(代理过滤器,非真正的过滤器,真正的过滤器需要在spring的配置文件)

2:springmvc的核心控制器

在web.xml中主要配置SpringMVC的DispatcherServlet和用于整合第三方框架的DelegatingFilterProxy(代理过滤器,真正的过滤器在spring的配置文件),用于整合Spring Security。

<?xml version="1.0" encoding="UTF-8"?>
<web-app 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/javaeehttp://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"version="3.0"><filter>
<!--  filter-name:固定为springSecurityFilterChain,不能修改。容器启动时,
会从spring容器中获取这个filtername的bean对象(security启动时创建的bean name)
可以修改:必须在spring容器中配置一个bean对象为你修改后的名称--><filter-name>springSecurityFilterChain</filter-name>
<!--   代理过滤:自己不干活,拦截的请求转给security的过滤器(springSecurityFilterChain)去处理--><filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class></filter><filter-mapping><filter-name>springSecurityFilterChain</filter-name><url-pattern>/*</url-pattern></filter-mapping><!-- 使用DispatcherServlet或者使用contextLoaderListener 都一样,只要启动容器即可   --><servlet><servlet-name>dispatchServlet</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><init-param><param-name>contextConfigLocation</param-name><param-value>classpath:spring-security.xml</param-value></init-param><load-on-startup>1</load-on-startup></servlet><servlet-mapping><servlet-name>dispatchServlet</servlet-name><url-pattern>*.do</url-pattern></servlet-mapping>
</web-app>

1.4.3. 配置spring-security.xml

【路径 】

1:定义哪些链接可以放行

2:定义哪些链接不可以放行,即需要有角色、权限才可以放行

3:认证管理,定义登录账号名和密码,并授予访问的角色、权限

在spring-security.xml中主要配置Spring Security的拦截规则和认证管理器。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:security="http://www.springframework.org/schema/security"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd">
<!--
【重要】【重要】【重要】
这里面的所有路径必须以/开头,否则启动报错
-->
<!--  auto-config: 自动配置,自动生成login页面,login处理,退出处理use-expressions: 是否使用spel表达式 true: access的值可以填表达式(hasRole, hasAuthority, hasAny....)false: ROEL_角色名(必须是ROLE_打,否则启动报错), 或 security写死的几个常量-->
<!--  拦截规则配置  --><security:http auto-config="true" use-expressions="false">
<!--  pattern="/**" 拦截所有的路径  access="ROLE_ADMIN"要访问符合pattern的url时,登陆用户必须有ROLE_ADMIN的角色,如果没有则不能访问security:intercept-url: 可以配置多个--><security:intercept-url pattern="/**" access="ROLE_ADMIN"/></security:http><!--  认证管理器  --><security:authentication-manager>
<!--  认证信息提供者,认证信息的来源提供登陆用户信息  用户名、密码、权限集合user-service-ref 指向spring容器中一个bean对象, 实现这个UserDetailsService的对象, 提供用户的名称、密码、权限集合一旦配置了user-service-ref,就不要配置security:user-service
--><security:authentication-provider>
<!--   登陆用户信息由我们自己来提供         --><security:user-service>
<!--   security:user 硬编码一个用户对象在内存中,就不需要去查询数据库了将来应该使用从数据库查询name: 登陆的用户名  password:登陆的密码authorities: 指定的权限(既可以是角色名也可以权限名) authorities与上面access一致才能访问{noop}使用的是明文,no operation 不要对密码做处理--><security:user name="admin" password="{noop}admin" authorities="ROLE_ADMIN"/></security:user-service></security:authentication-provider></security:authentication-manager>
</beans>

{noop}:表示当前使用的密码为明文。表示当前密码不需要加密

自动跳转到登录页面(springSecurity自动提供的)

输入错误用户名和密码

输入正确用户名和密码(admin/admin)

此时说明没有登录成功的页面。

如果新建index.html,可以正常访问index.html

【小结】

使用步骤

  1. 创建Maven工程, 添加坐标
  2. 配置web.xml(前端控制器,springSecurity权限相关的过滤器 DelegatingFilterProxy, 过滤器的名称不能修改,一定是springSecurityFilterChain)
  3. 创建spring-security.xml(security:http 自动配置,使用表达式的方式完成授权,只要具有ROLE_ADMIN的角色权限才能访问系统中的所有功能; 授权管理器,指定用户名admin,密码{noop}admin,具有ROLE_ADMIN的角色权限)

注意实现

​ 1.在web.xml里面配置的权限相关的过滤器名字不能改(springSecurityFilterChain)

<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>

​ 2.入门案例里面没有指定密码加密方式的. 配置密码的时候的加{noop}

<security:user-service><security:user name="admin" password="{noop}admin" authorities="ROLE_ADMIN"/>
</security:user-service>

1.5. Spring Security进阶

【目标】

前面我们已经完成了Spring Security的入门案例,通过入门案例我们可以看到,Spring Security将我们项目中的所有资源都保护了起来,要访问这些资源必须要完成认证而且需要具有ROLE_ADMIN角色。

但是入门案例中的使用方法离我们真实生产环境还差很远,还存在如下一些问题:

1、项目中我们将所有的资源(所有请求URL pattern=/**)都保护起来,实际环境下往往有一些资源不需要认证也可以访问,也就是可以匿名访问(静态资源)。

2、登录页面是由框架生成的,而我们的项目往往会使用自己的登录页面。

3、直接将用户名和密码配置在了配置文件中,而真实生产环境下的用户名和密码往往保存在数据库中。

4、在配置文件中配置的密码使用明文,这非常不安全,而真实生产环境下密码需要进行加密。

本章节需要对这些问题进行改进。

【路径】

1:配置可匿名访问的资源(不需要登录,权限 角色 就可以访问的资源)

2:使用指定的登录页面(login.html)

3:从数据库查询用户信息

4:对密码进行加密

5:配置多种校验规则(对访问的页面做权限控制)

6:注解方式权限控制(对访问的Controller类做权限控制)

7:退出登录

【讲解】

1.5.1. 配置可匿名访问的资源

【路径】

1:在项目中创建js、css目录并在两个目录下提供任意一些测试文件

2:在spring-security.xml文件中配置,指定哪些资源可以匿名访问

第一步:在项目中创建js、css目录并在两个目录下提供任意一些测试文件

访问http://localhost:85/js/vue.js

第二步:在spring-security.xml文件中配置,指定哪些资源可以匿名访问

<!--http:用于定义相关权限控制指定哪些资源不需要进行权限校验,可以使用通配符
-->
<security:http security="none" pattern="/js/**" />
<security:http security="none" pattern="/css/**" />

通过上面的配置可以发现,js和css目录下的文件可以在没有认证的情况下任意访问。

1.5.2. 使用指定的登录页面

【路径】

1:提供login.html作为项目的登录页面

2:修改spring-security.xml文件,指定login.html页面可以匿名访问

3:修改spring-security.xml文件,加入表单登录信息的配置

4:修改spring-security.xml文件,关闭csrfFilter过滤器

【讲解】

第一步:提供login.html作为项目的登录页面

1:用户名是abc

2:密码是bbb

3:登录的url是/login

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<form name='f' action='/login' method='POST'><table><tr><td>用户名:</td><td><input type='text' name='abc' value=''></td></tr><tr><td>密码:</td><td><input type='password' name='bbb'/></td></tr><tr><td colspan='2'><input name="submit" type="submit" value="登陆"/></td></tr></table>
</form>
</body>
</html>

第二步:修改spring-security.xml文件,指定login.html页面可以匿名访问,否则无法访问。

<security:http security="none" pattern="/login.html" />

第三步:修改spring-security.xml文件,加入表单登录信息的配置

<!--   登陆配置 form-login
login-page: 登陆页面
username-parameter: 前端传过来的用户名的参数名
password-parameter: 前端传过来的密码的参数名
login-processing-url: 处理登陆请求的url
default-target-url: 登陆成功后默认跳转的页面, success.html -> login.html->success.html
always-use-default-target: true不管是从哪个页面转到login.html,登陆后都跑到default-target-urlsuccess.html -> login.html-> index.html
authentication-failure-url: 登陆失败后跳转的页面
--><security:form-loginlogin-page="/login.html"username-parameter="abc"password-parameter="bbb"login-processing-url="/login"default-target-url="/index.html"always-use-default-target="true"authentication-failure-url="/fail.html"></security:form-login>

第四步:修改spring-security.xml文件,关闭CsrfFilter过滤器

1.5.2.1. 注意1:

如果用户名和密码输入不正确/正确。抛出异常:

分析原因:

Spring-security采用盗链机制,其中_csrf使用token标识和随机字符,每次访问页面都会随机生成,然后和服务器进行比较,成功可以访问,不成功不能访问(403:权限不足)。

解决方案:

<!--关闭盗链安全请求-->
<security:csrf disabled="true" />

1.5.2.2. 注意2:

1:创建test.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>我是test页面
</body>
</html>

2:先访问test.html页面,跳转到login.html

3:再使用admin、admin登录,会跳转到test.html,怎么办?

分析原因:

登录成功后,没有始终跳转到成功页面,而是跳转到了之前访问的页面。

解决方案:

always-use-default-target=“true”

<!--登录页面配置login-page:登录页面login-processing-url:处理登录的地址default-target-url:登录成功后默认跳转地址authentication-failure-url:登录失败跳转地址always-use-default-target="true":登录成功后,始终跳转到default-target-url指定的地址,即登录成功的默认地址-->
<security:form-login login-page="/login.html"username-parameter="username"password-parameter="password"login-processing-url="/login.do"default-target-url="/index.html"authentication-failure-url="/login.html"always-use-default-target="true"
/>

1.5.3. 从数据库查询用户信息

【路径】

1:定义UserService类,实现UserDetailsService接口。

2:修改spring-security.xml配置(注册且注入UserService)

如果我们要从数据库动态查询用户信息,就必须按照spring security框架的要求提供一个实现UserDetailsService接口的实现类,并按照框架的要求进行配置即可。框架会自动调用实现类中的方法并自动进行密码校验。

第一步:定义UserService类,实现UserDetailsService接口。

实现类代码:

package com.itheima.security;import com.google.gson.internal.$Gson$Preconditions;
import com.itheima.health.pojo.Permission;
import com.itheima.health.pojo.Role;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;/*** Description: 将来要进入spring容器, 服务也可以注入进来* User: Eric*/
public class UserService implements UserDetailsService {/*** 通过用户名加载用户信息 User登陆用* @param username* @return* @throws UsernameNotFoundException*/@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//提供用户的名称、密码、权限集合// 通过用户名来查询数据库, 查询角色及权限com.itheima.health.pojo.User userInDB = findByUsername(username);// String username,// String password, 数据库中的密码, 密码校验(security帮我们做了)// Collection<? extends GrantedAuthority> authorities 权限集合List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();// 遍历用户身上的角色Set<Role> roles = userInDB.getRoles();if(null != roles){for (Role role : roles) {// 授予的权限// 构造方法要的是一个角色名GrantedAuthority ga = new SimpleGrantedAuthority(role.getName());authorities.add(ga);for (Permission permission : role.getPermissions()) {// 权限ga = new SimpleGrantedAuthority(permission.getName());authorities.add(ga);}}}// 登陆用户的认证信息,名称、密码、权限集合User user = new User(username,"{noop}" + userInDB.getPassword(),authorities);return user;}/*** 假设从数据库查询* @param username* @return*/private com.itheima.health.pojo.User findByUsername (String username){if("admin".equals(username)) {com.itheima.health.pojo.User user = new com.itheima.health.pojo.User();user.setUsername("admin");user.setPassword("admin");Role role = new Role();role.setName("ROLE_ADMIN");Permission permission = new Permission();permission.setName("ADD_CHECKITEM");role.getPermissions().add(permission);Set<Role> roleList = new HashSet<Role>();roleList.add(role);user.setRoles(roleList);return user;}return null;}public static void main(String[] args) {BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();// 加密密码//System.out.println(encoder.encode("1234"));// 验证密码// 原密码// 加密后的密码System.out.println(encoder.matches("1234", "$2a$10$P7Qx8eKUPX5lngz9UEstUOaDRldEWrj9Rbyox/ShyeoxvPbEHTvni"));System.out.println(encoder.matches("1234", "$2a$10$5q.0a0F0hRix8TBJxQ4DB.ekwGzPs3e47hoQVNR7cihi/Rob.G3T6"));System.out.println(encoder.matches("1234", "$2a$10$voh.1PJRXQazoijK72sIoOslpmLYPyB.6LtT7aUrXqUO5G8Aw43we"));System.out.println(encoder.matches("1234", "$2a$10$u/BcsUUqZNWUxdmDhbnoeeobJy6IBsL1Gn/S0dMxI2RbSgnMKJ.4a"));}
}

第二步:spring-security.xml:

本章节我们提供了UserService实现类,并且按照框架的要求实现了UserDetailsService接口。在spring配置文件中注册UserService,指定其作为认证过程中根据用户名查询用户信息的处理类。当我们进行登录操作时,spring security框架会调用UserService的loadUserByUsername方法查询用户信息,并根据此方法中提供的密码和用户页面输入的密码进行比对来实现认证操作。

1.5.4. 对密码进行加密

前面我们使用的密码都是明文的,这是非常不安全的。一般情况下用户的密码需要进行加密后再保存到数据库中。

常见的密码加密方式有:

3DES、AES、DES:使用对称加密算法,可以通过解密来还原出原始密码 可逆加密

MD5、SHA1:使用单向HASH算法,无法通过计算还原出原始密码,但是可以建立彩虹表进行查表破解

MD5可进行加盐加密,保证安全

public class TestMD5 {@Testpublic void testMD5(){// 密码同样是1234却变成了密码不相同System.out.println(MD5Utils.md5("1234xiaowang")); //a8231077b3d5b40ffadee7f4c8f66cb7System.out.println(MD5Utils.md5("1234xiaoli")); //7d5250d8620fcdb53b25f96a1c7be591}
}

同样的密码值,盐值不同,加密的结果不同。

bcrypt:将salt随机并混入最终加密后的密码,验证时也无需单独提供之前的salt,从而无需单独处理salt问题

spring security中的BCryptPasswordEncoder方法采用SHA-256 +随机盐+密钥对密码进行加密。SHA系列是Hash算法,不是加密算法,使用加密算法意味着可以解密(这个与编码/解码一样),但是采用Hash处理,其过程是不可逆的。

(1)加密(encode):注册用户时,使用SHA-256+随机盐+密钥把用户输入的密码进行hash处理,得到密码的hash值,然后将其存入数据库中。

(2)密码匹配(matches):用户登录时,密码匹配阶段并没有进行密码解密(因为密码经过Hash处理,是不可逆的),而是使用相同的算法把用户输入的密码进行hash处理,得到密码的hash值,然后将其与从数据库中查询到的密码hash值进行比较。如果两者相同,说明用户输入的密码正确。

这正是为什么处理密码时要用hash算法,而不用加密算法。因为这样处理即使数据库泄漏,黑客也很难破解密码。

建立测试代码

package com.itheima.security.test;import org.junit.Test;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;public class TestSpringSecurity {// SpringSecurity加盐加密@Testpublic void testSpringSecurity(){BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();// 加密密码//System.out.println(encoder.encode("1234"));// 验证密码// 原密码// 加密后的密码System.out.println(encoder.matches("1234", "$2a$10$P7Qx8eKUPX5lngz9UEstUOaDRldEWrj9Rbyox/ShyeoxvPbEHTvni"));System.out.println(encoder.matches("1234", "$2a$10$5q.0a0F0hRix8TBJxQ4DB.ekwGzPs3e47hoQVNR7cihi/Rob.G3T6"));System.out.println(encoder.matches("1234", "$2a$10$voh.1PJRXQazoijK72sIoOslpmLYPyB.6LtT7aUrXqUO5G8Aw43we"));System.out.println(encoder.matches("1234", "$2a$10$u/BcsUUqZNWUxdmDhbnoeeobJy6IBsL1Gn/S0dMxI2RbSgnMKJ.4a"));}
}

加密后的格式一般为:

$2a$10$/bTVvqqlH9UiE0ZJZ7N2Me3RIgUCdgMheyTgV0B4cMCSokPa.6oCa

加密后字符串的长度为固定的60位。其中:

$是分割符,无意义;

2a是bcrypt加密版本号;

10是cost的值;

而后的前22位是salt值;

再然后的字符串就是密码的密文了。

实现步骤:

【路径】

1:在spring-security.xml文件中指定密码加密对象

2:修改UserService实现类

【讲解】

第一步:在spring-security.xml文件中指定密码加密对象

<!--三:认证管理,定义登录账号名和密码,并授予访问的角色、权限authentication-manager:认证管理器,用于处理认证操作
-->
<security:authentication-manager><!--authentication-provider:认证提供者,执行具体的认证逻辑--><security:authentication-provider user-service-ref="userService"><!--指定密码加密策略--><security:password-encoder ref="encoder"></security:password-encoder></security:authentication-provider>
</security:authentication-manager><!--配置密码加密对象-->
<bean id="encoder"class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />

第二步:修改UserService实现类

package com.itheima.security;import com.google.gson.internal.$Gson$Preconditions;
import com.itheima.health.pojo.Permission;
import com.itheima.health.pojo.Role;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;/*** Description: 将来要进入spring容器, 服务也可以注入进来* User: Eric*/
public class UserService implements UserDetailsService {/*** 通过用户名加载用户信息 User登陆用* @param username* @return* @throws UsernameNotFoundException*/@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//提供用户的名称、密码、权限集合// 通过用户名来查询数据库, 查询角色及权限com.itheima.health.pojo.User userInDB = findByUsername(username);// String username,// String password, 数据库中的密码, 密码校验(security帮我们做了)// Collection<? extends GrantedAuthority> authorities 权限集合List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();// 遍历用户身上的角色/*Set<Role> roles = userInDB.getRoles();if(null != roles){for (Role role : roles) {// 授予的权限// 构造方法要的是一个角色名GrantedAuthority ga = new SimpleGrantedAuthority(role.getName());authorities.add(ga);for (Permission permission : role.getPermissions()) {// 权限ga = new SimpleGrantedAuthority(permission.getName());authorities.add(ga);}}}*///测试其它方式的认证GrantedAuthority ga = new SimpleGrantedAuthority("ROLE_ADMIN");authorities.add(ga);ga = new SimpleGrantedAuthority("add");authorities.add(ga);// 登陆用户的认证信息,名称、密码、权限集合//User user = new User(username,"{noop}" + userInDB.getPassword(),authorities);// 使用加密的密码后,去除{noop}User user = new User(username,userInDB.getPassword(),authorities);return user;}/*** 假设从数据库查询* @param username* @return*/private com.itheima.health.pojo.User findByUsername (String username){if("admin".equals(username)) {com.itheima.health.pojo.User user = new com.itheima.health.pojo.User();user.setUsername("admin");//user.setPassword("admin");// 使用密码加密器encoder, 加密后的密码user.setPassword("$2a$10$P7Qx8eKUPX5lngz9UEstUOaDRldEWrj9Rbyox/ShyeoxvPbEHTvni");Role role = new Role();role.setName("ROLE_ADMIN");Permission permission = new Permission();permission.setName("ADD_CHECKITEM");role.getPermissions().add(permission);Set<Role> roleList = new HashSet<Role>();roleList.add(role);user.setRoles(roleList);return user;}return null;}public static void main(String[] args) {BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();// 加密密码//System.out.println(encoder.encode("1234"));// 验证密码// 原密码// 加密后的密码System.out.println(encoder.matches("1234", "$2a$10$P7Qx8eKUPX5lngz9UEstUOaDRldEWrj9Rbyox/ShyeoxvPbEHTvni"));System.out.println(encoder.matches("1234", "$2a$10$5q.0a0F0hRix8TBJxQ4DB.ekwGzPs3e47hoQVNR7cihi/Rob.G3T6"));System.out.println(encoder.matches("1234", "$2a$10$voh.1PJRXQazoijK72sIoOslpmLYPyB.6LtT7aUrXqUO5G8Aw43we"));System.out.println(encoder.matches("1234", "$2a$10$u/BcsUUqZNWUxdmDhbnoeeobJy6IBsL1Gn/S0dMxI2RbSgnMKJ.4a"));}
}

使用密码加密器小结:

  1. security.xml添加bean 加密器

    <!-- 加密器-->
    <bean id="encoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
    
  2. security.xml使用加密器

    <security:authentication-provider user-service-ref="userService"><!--  provider调用 service 来获取认证用户信息  --><!--   <security:user-service>写死内存中一个用户信息authorities: 这个用户所拥有的权限/角色明文密码:  noop no operation密码加密器:默认使用BcyptPasswordEncoder<security:user name="admin" authorities="ROLE_ADMIN" password="{noop}admin"/></security:user-service> --><security:password-encoder ref="encoder"/>
    </security:authentication-provider>
    
  3. 修改UserService中admin用户的密码为密文, 去{noop}

1.5.5. 配置多种验证规则(对页面)

为了测试方便,首先在项目中创建a.html、b.html、c.html、d.html几个页面

修改spring-security.xml文件:

前提:<security:http auto-config=“true” use-expressions=“true”>,开启对表达式的支持

<!--只要认证通过就可以访问-->
<security:intercept-url pattern="/index.html"  access="isAuthenticated()" />
<security:intercept-url pattern="/a.html"  access="isAuthenticated()" /><!--拥有add权限就可以访问b.html页面-->
<security:intercept-url pattern="/b.html"  access="hasAuthority('add')" /><!--拥有ROLE_ADMIN角色就可以访问c.html页面,注意:此处虽然写的是ADMIN角色,框架会自动加上前缀ROLE_-->
<security:intercept-url pattern="/c.html"  access="hasRole('ADMIN')" /><!--拥有ROLE_ADMIN角色就可以访问d.html页面-->
<security:intercept-url pattern="/d.html"  access="hasRole('ABC')" />

测试:

登录后可以访问a.html,b.html,c.html,不能访问d.html(抛出403的异常)

1.5.6. 注解方式权限控制(对类)

Spring Security除了可以在配置文件中配置权限校验规则,还可以使用注解方式控制类中方法的调用。例如Controller中的某个方法要求必须具有某个权限才可以访问,此时就可以使用Spring Security框架提供的注解方式进行控制。

【路径】

1:在spring-security.xml文件中配置组件扫描和mvc的注解驱动,用于扫描Controller

2:在spring-security.xml文件中开启权限注解支持

3:创建Controller类并在Controller的方法上加入注解(@PreAuthorize)进行权限控制

实现步骤:

第一步:在spring-security.xml文件中配置组件扫描,用于扫描Controller

<context:component-scan base-package="com.itheima"/>
<mvc:annotation-driven></mvc:annotation-driven>

第二步:在spring-security.xml文件中开启权限注解支持

<!--开启注解方式权限控制-->
<security:global-method-security pre-post-annotations="enabled" />

第三步:创建Controller类并在Controller的方法上加入注解(@PreAuthorize)进行权限控制

package com.itheima.controller;import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;@Controller
@RequestMapping("/hello")
public class HelloController {@RequestMapping("/add")@PreAuthorize("hasAuthority('add')")//表示用户必须拥有add权限才能调用当前方法public String add(){System.out.println("add...");return null;}@RequestMapping("/update")@PreAuthorize("hasRole('ROLE_ADMIN')")//表示用户必须拥有ROLE_ADMIN角色才能调用当前方法public String update(){System.out.println("update...");return null;}@RequestMapping("/delete")@PreAuthorize("hasRole('ABC')")//表示用户必须拥有ABC角色才能调用当前方法public String delete(){System.out.println("delete...");return null;}
}

测试delete方法不能访问

小结:

  1. 开启注解支持

    <security:global-method-security pre-post-annotations="enabled"/>
    
  2. 使用mvc:annotation-driven

  3. 创建controller,且加入扫包context:component-scan

  4. controller方法@PreAuthorize(hasAuthority/hasRole)

  5. 注解掉

    <!--<security:intercept-url pattern="/**" access="hasRole('ROLE_ADMIN')"/>-->
    

URL拦截权限配置,粗颗粒

@PreAuthrize 细颗粒(具体的方法) @PreAuthrize用controller类上

如果类有PreAuthrize, 方法上也有PreAuthrize, 两个条件都满足后才可以访问

推荐:使用角色来做权限控制

1.5.7. 退出登录

用户完成登录后Spring Security框架会记录当前用户认证状态为已认证状态,即表示用户登录成功了。那用户如何退出登录呢?我们可以在spring-security.xml文件中进行如下配置:

【路径】

1:index.html定义退出登录链接

2:在spring-security.xml定义

【讲解】

第一步:index.html定义退出登录链接

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>登录成功!<br><a href="/logout">退出登录</a>
</body>
</html>

第二步:在spring-security.xml定义:

<!--logout:退出登录logout-url:退出登录操作对应的请求路径logout-success-url:退出登录后的跳转页面
-->
<security:logout logout-url="/logout"logout-success-url="/login.html" invalidate-session="true"/>

​ 通过上面的配置可以发现,如果用户要退出登录,只需要请求/logout.do这个URL地址就可以,同时会将当前session失效,最后页面会跳转到login.html页面。

【小结】

1:配置可匿名访问的资源(不需要登录,权限 角色 就可以访问)

<security:http security="none" pattern="/js/**"></security:http>
<security:http security="none" pattern="/css/**"></security:http>
<security:http security="none" pattern="/login.html"></security:http>

2:使用指定的登录页面(login.html)

<security:form-login login-page="/login.html"username-parameter="username"password-parameter="password"login-processing-url="/login"default-target-url="/index.html"authentication-failure-url="/login.html"always-use-default-target="true"/>
<!--    关闭跨域访问限制    -->
<security:csrf disabled="true"/>

3:从数据库查询用户信息

添加类实现UserDetailService接口,实现loadByUsername方法,且要返回UserDetails对象(用户名,数据库中的密码,用户所拥有的权限集合)

<security:authentication-manager><security:authentication-provider user-service-ref="userService"><security:password-encoder ref="encoder"></security:password-encoder></security:authentication-provider>
</security:authentication-manager>

4:对密码进行加密

<bean id="encoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"></bean>userDetail中密码不再用{noop},把{noop}删除

5:配置多种校验规则(对访问的页面做权限控制, 多个时,从上往按顺序,只要有一个满足,就会处理)

use-expressions="true"
<security:intercept-url pattern="/index.html" access="isAuthenticated()"></security:intercept-url>
<security:intercept-url pattern="/a.html" access="isAuthenticated()"></security:intercept-url>
<security:intercept-url pattern="/b.html" access="hasAuthority('add')"></security:intercept-url>
<security:intercept-url pattern="/c.html" access="hasRole('ROLE_ADMIN')"></security:intercept-url>
<security:intercept-url pattern="/d.html" access="hasRole('ABC')"></security:intercept-url>

6:注解方式权限控制(对访问的Controller类做权限控制)

<security:global-method-security pre-post-annotations="enabled"></security:global-method-security>

同时使用注解:

在Controller类中的方法上添加:@PreAuthorize(value = “hasRole(‘ROLE_ADMIN’)”)

7:退出登录

<security:logout logout-url="/logout" logout-success-url="/login.html" invalidate-session="true"></security:logout>

2. 项目中使用Spring Security【重点】

前面我们已经学习了Spring Security框架的使用方法,本章节我们就需要将Spring Security框架应用到后台系统中进行权限控制,其本质就是认证和授权。

要进行认证和授权需要前面课程中提到的权限模型涉及的7张表支撑,因为用户信息、权限信息、菜单信息、角色信息、关联信息等都保存在这7张表中,也就是这些表中的数据是我们进行认证和授权的依据。所以在真正进行认证和授权之前需要对这些数据进行管理,即我们需要开发如下一些功能:

1、用户数据管理(增删改查、用户关联角色)

2、角色数据管理(增删改查、角色关联权限、角色关联菜单)

3、权限数据管理(增删改查)

4、菜单数据管理(增删改查)

鉴于时间关系,我们不再实现这些数据管理的代码开发。我们可以直接将数据导入到数据库中即可。

导入用户、角色、权限、菜单的初始数据

【目标】

在传智健康的项目中使用SpringSecurity完成认证和授权

【路径】

1:导入SpringSecurity环境

(1)pom.xml中添加依赖

(2)health_web web.xml添加代理过滤器

2:实现认证和授权

(1)导入login.html,放入health_web工程的webapp目录下

(2)认证:SpringSecurityUserService.java

(3)创建UserService类、UserDao接口类、UserDao映射文件

(4)springmvc.xml(dubbo注解扫描范围扩大)

(5)spring-security.xml

  • 静态资源
  • 拦截规则,所有都必须登陆后才可访问
  • 登陆页面配置
  • 关闭csrf
  • frame访问策略
  • 退出登陆
  • 开启注解权限控制
  • 认证管理器
    • 认证信息提供者
    • 加密器

(6)springmvc.xml(导入spring-security.xml)

(7)CheckItemController类(@PreAuthorize(“hasAuthority(‘CHECKITEM_ADD’)”):完成权限)

(8)捕获异常

3:显示用户名

4:用户退出

【讲解】

2.1. 导入Spring Security环境

【路径】

1:pom.xml导入坐标

2:web.xml添加代理过滤器

2.1.1. 第一步:pom.xml导入坐标

在health_parent父工程的pom.xml中导入Spring Security的maven坐标(已经引入)

<dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-web</artifactId><version>${spring.security.version}</version>
</dependency>
<dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-config</artifactId><version>${spring.security.version}</version>
</dependency>

2.1.2. 第二步:web.xml添加代理过滤器

在health_web工程的web.xml文件中配置用于整合Spring Security框架的过滤器DelegatingFilterProxy

<filter><!--DelegatingFilterProxy用于整合第三方框架(代理过滤器,非真正的过滤器,真正的过滤器需要在spring的配置文件)整合Spring Security时过滤器的名称必须为springSecurityFilterChain,否则会抛出NoSuchBeanDefinitionException异常--><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>

2.2. 实现认证和授权

2.2.1. 第一步:导入login.html页面

此时login.html是可放行的页面,而pages下的页面必须认证之后才能访问的页面

2.2.2. 第二步:SpringSecurityUserService.java

在health_web工程中按照Spring Security框架要求提供SpringSecurityUserService,并且实现UserDetailsService接口

package com.itheima.health.security;import com.alibaba.dubbo.config.annotation.Reference;
import com.itheima.health.pojo.Permission;
import com.itheima.health.pojo.Role;
import com.itheima.health.pojo.User;
import com.itheima.health.service.UserService;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;import java.util.ArrayList;
import java.util.List;
import java.util.Set;/*** Description: 登陆用户认证与授权* 记得要把这个类注册到spring容器* User: Eric*/
@Component
public class SpringSecurityUserService implements UserDetailsService {@Referenceprivate UserService userService;/*** 提供登陆用户信息  username password 权限集合 authorities* @param username* @return* @throws UsernameNotFoundException*/@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//根据登陆用户名称查询用户权限信息//t_user -> t_user_role -> t_role -> t_role_permission -> t_permission//找出用户所拥有的角色,及角色下所拥有的权限集合//User.roles(角色集合).permissions(权限集合)User user = userService.findByUsername(username);if(null != user){// 用户名// 密码String password = user.getPassword();// 权限集合List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();// 授权// 用户所拥有的角色SimpleGrantedAuthority sai = null;Set<Role> roles = user.getRoles();if(null != roles){for (Role role : roles) {// 角色用关键字, 授予角色sai = new SimpleGrantedAuthority(role.getKeyword());authorities.add(sai);// 权限, 角色下的所有权限Set<Permission> permissions = role.getPermissions();if(null != permissions){for (Permission permission : permissions) {// 授予权限sai = new SimpleGrantedAuthority(permission.getKeyword());authorities.add(sai);}}}}return new org.springframework.security.core.userdetails.User(username,password,authorities);}// 返回null, 限制访问return null;}
}

使用debug跟踪调试,查看user。

2.2.3. 第三步:Service、Dao接口、Mapper映射文件

创建UserService服务接口、服务实现类、Dao接口、Mapper映射文件等

【路径】

1:UserService.java接口

2:UserServiceImpl.java类

3:UserDao.java(使用用户名称查询用户)

4:RoleDao.java(使用用户id查询角色集合)

5:PermissionDao.java(使用角色id查询权限集合)

6:UserDao.xml(使用用户名称查询用户)

7:RoleDao.xml(使用用户id查询角色集合)

8:PermissionDao.xml(使用角色id查询权限集合)

【讲解】

1:服务接口

package com.itheima.health.service;import com.itheima.health.pojo.User;/*** Description: 用户服务(企业员工)* User: Eric*/
public interface UserService {/*** 根据登陆用户名称查询用户权限信息* @param username* @return*/User findByUsername(String username);
}

2:服务实现类

package com.itheima.health.service.impl;import com.alibaba.dubbo.config.annotation.Service;
import com.itheima.health.dao.UserDao;
import com.itheima.health.pojo.User;
import com.itheima.health.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;/*** Description: No Description* User: Eric*/
@Service(interfaceClass = UserService.class)
public class UserServiceImpl implements UserService {@Autowiredprivate UserDao userDao;/*** 根据登陆用户名称查询用户权限信息* @param username* @return*/@Overridepublic User findByUsername(String username) {return userDao.findByUsername(username);}
}

3:Dao接口

(1)UserDao

package com.itheima.health.dao;import com.itheima.health.pojo.User;/*** Description: No Description* User: Eric*/
public interface UserDao {/*** 根据登陆用户名称查询用户权限信息* @param username* @return*/User findByUsername(String username);
}

4:Mapper映射文件

(1)UserDao.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.itheima.health.dao.UserDao"><select id="findByUsername" parameterType="String" resultMap="userRolePermissionMap">select u.id,u.username,u.password,ur.role_id, r.keyword role_keyword, r.name role_name,rp.permission_id, p.keyword permission_keyword, p.name permission_nameFromt_user u, t_user_role ur, t_role r,t_role_permission rp, t_permission pwhere u.id=ur.user_id and ur.role_id=r.idand r.id=rp.role_id and rp.permission_id=p.idand u.username=#{username}</select><resultMap id="userRolePermissionMap" type="User"><id property="id" column="id"></id><result property="username" column="username"/><result property="password" column="password"/><collection property="roles" ofType="Role"><id property="id" column="role_id"/><result property="keyword" column="role_keyword"/><result property="name" column="role_name"/><collection property="permissions" ofType="Permission"><id property="id" column="permission_id"/><result property="keyword" column="permission_keyword"/><result property="name" column="permission_name"/></collection></collection></resultMap>
</mapper>

2.2.4. 第四步:springmvc.xml

修改health_web工程中的springmvc.xml文件,修改dubbo批量扫描的包路径

之前的包扫描

<!--批量扫描-->
<dubbo:annotation package="com.itheima.health.controller" />

现在的包扫描

<!--批量扫描-->
<dubbo:annotation package="com.itheima.health" />

**注意:**此处原来扫描的包为com.itheima.controller,现在改为com.itheima包的目的是需要将我们上面定义的SpringSecurityUserService也扫描到,因为在SpringSecurityUserService的loadUserByUsername方法中需要通过dubbo远程调用名称为UserService的服务。

2.2.5. 第五步:spring-security.xml

【路径】

1:定义哪些链接可以放行

2:定义哪些链接不可以放行,即需要有角色、权限才可以放行

3:认证管理,定义登录账号名和密码,并授予访问的角色、权限

4:设置在页面可以通过iframe访问受保护的页面,默认为不允许访问,需要添加security:frame-options policy=“SAMEORIGIN”

【讲解】

在health_web工程中提供spring-security.xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:security="http://www.springframework.org/schema/security"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd">
<!--   静态资源(css, img, js..., login.html)--><security:http pattern="/css/**" security="none"/><security:http pattern="/img/**" security="none"/><security:http pattern="/js/**" security="none"/><security:http pattern="/plugins/**" security="none"/><security:http pattern="/login.html" security="none"/><!--拦截规则autoconfig userexpressintercepter-url  pattern access登陆页面退出登陆关闭csrf--><security:http auto-config="true" use-expressions="true"><security:intercept-url pattern="/**" access="isAuthenticated()"/><security:form-login login-page="/login.html"login-processing-url="/login.do"username-parameter="username"password-parameter="password"default-target-url="/pages/main.html"always-use-default-target="true"/><security:headers>
<!--   frame-options 控制页面中嵌套frame(访问其它页面,把其它页面的内容展示在这个页面上)policy 使用的策略:DENY: 不允许访问SAMEORIGIN: 同域可以访问ALLOW-FROM: 指定url可以访问--><security:frame-options policy="SAMEORIGIN"/></security:headers><security:csrf disabled="true"/><security:logout logout-url="/logout.do" logout-success-url="/login.html" invalidate-session="true"/></security:http><!--认证信息认证管理器提供者 user-service-ref  springSecurityUserService implements UserDetailsService配置加密器--><security:authentication-manager><security:authentication-provider user-service-ref="springSecurityUserService"><security:password-encoder ref="encoder"/></security:authentication-provider></security:authentication-manager><!--注册springSecurityUserService注册密码加密器--><!--<bean id="springSecurityUserService" class="com.itheima.health.security.SpringSecurityUserService"/>--><bean id="encoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/><!--  权限控制注解支持  --><security:global-method-security pre-post-annotations="enabled"/>
</beans>

这里注意:如果出现以下问题

使用下面的配置,在spring-security.xml中添加。

放置到<security:http auto-config=“true” use-expressions=“true”>里面

<security:headers><!--设置在页面可以通过iframe访问受保护的页面,默认为不允许访问--><security:frame-options policy="SAMEORIGIN"></security:frame-options>
</security:headers>

是因为我们在main.html中定义:如果不配置springSecurity会认为iframe访问的html页面是收保护的页面,不允许访问。

<el-container><iframe name="right" class="el-main" src="checkitem.html" width="100%" height="580px" frameborder="0"></iframe>
</el-container>

备注:

2.2.6. 第六步:springmvc.xml

在springmvc.xml文件中引入spring-security.xml文件

<import resource="classpath:spring-security.xml"></import>

2.2.7. 第七步:CheckItemController类

在Controller的方法上加入权限控制注解,此处以CheckItemController为例

package com.itheima.health.controller;import com.alibaba.dubbo.config.annotation.Reference;
import com.itheima.health.constant.MessageConstant;
import com.itheima.health.entity.PageResult;
import com.itheima.health.entity.QueryPageBean;
import com.itheima.health.entity.Result;
import com.itheima.health.pojo.CheckItem;
import com.itheima.health.service.CheckItemService;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;import java.util.List;/*** Description: No Description* User: Eric*/
@RestController
@RequestMapping("/checkitem")
public class CheckItemController {@Referenceprivate CheckItemService checkItemService;@GetMapping("/findAll")public Result findAll(){// 调用服务查询所有的检查项List<CheckItem> list = checkItemService.findAll();// 封装返回的结果return new Result(true, MessageConstant.QUERY_CHECKITEM_SUCCESS,list);}/*** 新增检查项* @param checkitem* @return*/@PostMapping("/add")@PreAuthorize("hasAuthority('CHECKITEM_ADD')")public Result add(@RequestBody CheckItem checkitem){// 调用业务服务checkItemService.add(checkitem);// 响应结果给前端return new Result(true, MessageConstant.ADD_CHECKITEM_SUCCESS);}/*** 分页查询*/@PostMapping("/findPage")@PreAuthorize("hasAuthority('CHECKITEM_QUERY')")public Result findPage(@RequestBody QueryPageBean queryPageBean){// 调用业务来分页PageResult<CheckItem> pageResult = checkItemService.findPage(queryPageBean);//return pageResult;// 返回给页面, 包装到Result, 统一风格return new Result(true, MessageConstant.QUERY_CHECKITEM_SUCCESS,pageResult);}/*** 删除*/@PostMapping("/deleteById")public Result deleteById(int id){// 调用业务服务//try {checkItemService.deleteById(id);//} catch (Exception e) {//   e.printStackTrace();//}// 响应结果return new Result(true, MessageConstant.DELETE_CHECKITEM_SUCCESS);}/*** 通过id查询*/@GetMapping("/findById")public Result findById(int id){CheckItem checkItem = checkItemService.findById(id);return new Result(true, MessageConstant.QUERY_CHECKITEM_SUCCESS,checkItem);}/*** 修改检查项* @param checkitem* @return*/@PostMapping("/update")public Result update(@RequestBody CheckItem checkitem){// 调用业务服务checkItemService.update(checkitem);// 响应结果给前端return new Result(true, MessageConstant.EDIT_CHECKITEM_SUCCESS);}
}

2.2.8. 第八步:异常捕获

修改health_web项目中的HealthExceptionAdvice

package com.itheima.health.controller;import com.itheima.health.entity.Result;
import com.itheima.health.exception.HealthException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;/*** Description: No Description* User: Eric*/
// 与前端约定好的,返回的都是json数据
@RestControllerAdvice
public class HealExceptionAdvice {/*** 自定义招出的异常处理* @param he* @return*/@ExceptionHandler(HealthException.class)public Result handleHealthException(HealthException he){return new Result(false, he.getMessage());}/*** 所有未知的异常* @param he* @return*/@ExceptionHandler(Exception.class)public Result handleException(Exception he){he.printStackTrace();return new Result(false, "发生未知错误,操作失败,请联系管理员");}/*** 密码错误* @param he* @return*/@ExceptionHandler(BadCredentialsException.class)public Result handBadCredentialsException(BadCredentialsException he){return handleUserPassword();}/*** 用户名不存在* @param he* @return*/@ExceptionHandler(InternalAuthenticationServiceException.class)public Result handInternalAuthenticationServiceException(InternalAuthenticationServiceException he){return handleUserPassword();}private Result handleUserPassword(){return new Result(false, "用户名或密码错误");}/*** 用户名不存在* @param he* @return*/@ExceptionHandler(AccessDeniedException.class)public Result handAccessDeniedException(AccessDeniedException he){return new Result(false, "没有权限");}
}

2.3. 显示用户名

【路径】

1:引入js

2:定义loginUsername属性

3:使用钩子函数,调用ajax,查询登录用户(从SpringSecurity中获取),赋值username属性

4:修改页面,使用{{loginUsername}}显示用户信息

【讲解】

前面我们已经完成了认证和授权操作,如果用户认证成功后需要在页面展示当前用户的用户名。Spring Security在认证成功后会将用户信息保存到框架提供的上下文对象中,所以此处我们就可以调用Spring Security框架提供的API获取当前用户的username并展示到页面上。

实现步骤:

第一步:在main.html页面中修改,定义username模型数据基于VUE的数据绑定展示用户名,发送ajax请求获取username

(1):引入js

<script src="../js/axios-0.18.0.js"></script>

(2):定义loginUsername属性

(3):使用钩子函数mounted,调用ajax

(4)显示用户名

页面最终如下

<!DOCTYPE html>
<html>
<head><!-- 页面meta --><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><title>传智健康</title><meta name="description" content="传智健康"><meta name="keywords" content="传智健康"><meta content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no" name="viewport"><!-- 引入样式 --><link rel="stylesheet" href="../plugins/elementui/index.css"><link rel="stylesheet" href="../plugins/font-awesome/css/font-awesome.min.css"><link rel="stylesheet" href="../css/style.css"><style type="text/css">.el-main{position: absolute;top: 70px;bottom: 0px;left: 200px;right: 10px;padding: 0;}</style>
</head>
<body class="hold-transition skin-purple sidebar-mini"><div id="app"><el-container><el-header  class="main-header" style="height:70px;"><nav class="navbar navbar-static-top" :class=''><!-- Logo --><a href="#" class="logo" style="text-align:center"><span class="logo-lg"><img src="../img/logo.png"></span></a><div class="right-menu"><span class="help"><i class="fa fa-exclamation-circle" aria-hidden="true"></i>帮助</span><el-dropdown class="avatar-container right-menu-item" trigger="click"><div class="avatar-wrapper"><img src="../img/user2-160x160.jpg" class="user-avatar">{{loginUsername}}</div><el-dropdown-menu slot="dropdown"><el-dropdown-item divided><span style="display:block;">修改密码</span></el-dropdown-item><el-dropdown-item divided><span style="display:block;"><a href="/logout.do">退出</a> </span></el-dropdown-item></el-dropdown-menu></el-dropdown></div></nav></el-header><el-container><el-aside width="200px"><el-menu><el-submenu v-for="menu in menuList" :index="menu.path"><template slot="title"><i class="fa" :class="menu.icon"></i>{{menu.title}}</template><template v-for="child in menu.children"><el-menu-item :index="child.path"><a :href="child.linkUrl" target="right">{{child.title}}</a></el-menu-item></template></el-submenu></el-menu></el-aside><el-container><iframe name="right" class="el-main" src="ordersetting.html" width="100%" height="580px" frameborder="0"></iframe></el-container></el-container></el-container></div>
</body>
<!-- 引入组件库 -->
<script src="../js/vue.js"></script>
<script src="../plugins/elementui/index.js"></script>
<script type="text/javascript" src="../js/jquery.min.js"></script>
<script src="../js/axios-0.18.0.js"></script>
<script>new Vue({el: '#app',data:{loginUsername:'',menuList:[{"path": "1","title": "工作台","icon":"fa-dashboard","children": []},{"path": "2","title": "会员管理","icon":"fa-user-md","children": [{"path": "/2-1","title": "会员档案","linkUrl":"member.html","children":[]},{"path": "/2-2","title": "体检上传","children":[]},{"path": "/2-3","title": "会员统计","linkUrl":"all-item-list.html","children":[]},]},{"path": "3","title": "预约管理","icon":"fa-tty","children": [{"path": "/3-1","title": "预约列表","linkUrl":"ordersettinglist.html","children":[]},{"path": "/3-2","title": "预约设置","linkUrl":"ordersetting.html","children":[]},{"path": "/3-3","title": "套餐管理","linkUrl":"setmeal.html","children":[]},{"path": "/3-4","title": "检查组管理","linkUrl":"checkgroup.html","children":[]},{"path": "/3-5","title": "检查项管理","linkUrl":"/pages/checkitem.html","children":[]},]},{"path": "4","title": "健康评估","icon":"fa-stethoscope","children":[{"path": "/4-1","title": "中医体质辨识","linkUrl":"all-medical-list.html","children":[]},]},{"path": "5",     //菜单项所对应的路由路径"title": "统计分析",     //菜单项名称"icon":"fa-heartbeat","children":[//是否有子菜单,若没有,则为[]{"path": "/5-1","title": "会员数量统计","linkUrl":"/pages/report_member.html","children":[]},{"path": "/5-2","title": "预约套餐占比","linkUrl":"/pages/report_setmeal.html","children":[]},{"path": "/5-3","title": "运营数据统计","linkUrl":"/pages/report_business.html","children":[]}]}]},mounted(){// 获取登陆用户名axios.get('/user/getUsername.do').then(res => {if(res.data.flag){this.loginUsername = res.data.data;}else{this.$message.error(res.data.message);}})}});$(function() {var wd = 200;$(".el-main").css('width', $('body').width() - wd + 'px');});</script>
</html>

第二步:创建UserController并提供getUsername方法

package com.itheima.health.controller;import com.itheima.health.constant.MessageConstant;
import com.itheima.health.entity.Result;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** Description: No Description* User: Eric*/
@RestController
@RequestMapping("/user")
public class UserController {/*** 获取登陆用户名*/@GetMapping("/getUsername")public Result getUsername(){// 获取登陆用户的认证信息User loginUser = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();// 登陆用户名String username = loginUser.getUsername();// 返回给前端return new Result(true, MessageConstant.GET_USERNAME_SUCCESS,username);}
}

通过debug调试可以看到Spring Security框架在其上下文中保存的用户相关信息:

显示当前登录人:

2.4. 用户退出

【路径】

1:在main.html中提供的退出菜单上加入超链接

2:在spring-security.xml文件中配置

【讲解】

第一步:在main.html中提供的退出菜单上加入超链接

<el-dropdown-item divided><span style="display:block;"><a href="/logout.do">退出</a></span>
</el-dropdown-item>

第二步:在spring-security.xml文件中配置

<!--logout:退出登录logout-url:退出登录操作对应的请求路径logout-success-url:退出登录后的跳转页面
-->
<security:logout logout-url="/logout.do"logout-success-url="/login.html" invalidate-session="true"/>

【小结】

1:导入SpringSecurity环境

(1)pom.xml中添加依赖

(2)web.xml添加代理过滤器

2:实现认证和授权

(1)导入login.html登录页面 webapp目录下

(2)认证:SpringSecurityUserService(@Component),实现UserDetailsService接口

(3)创建UserService类、UserDao接口类、UserDao映射文件(使用用户名查询当前用户信息,包括角色集合和权限集合)

(4)springmvc.xml(dubbo注解扫描范围扩大, 扫到SpringSecurityUserService)

(5)spring-security.xml(重点 存小抄 )

  • 静态资源过滤
  • 拦截的规则 security:http auto-config…, intercept-url, form-login, form-logout, csrf, security:header
  • 开启注解支持
  • 关闭跨域访问限制
  • 认证管理器->提供者user-service-ref->加密器
  • 加密器

(6)springmvc.xml(导入spring-security.xml)

(7)CheckItemController类(@PreAuthorize(“hasAuthority(‘CHECKITEM_ADD’)”):对类中的方法完成权限控制), hasAuthority 权限校验(t_permission.keyword), hasRole角色校验(t_role.keyword)

(8)checkitem.html(如果没有权限,可以提示错误信息)

异常捕获HealthExceptionHandler, AccessDeniedException, return 没有权限的result

3:显示用户名

从SecurityContextHolder对象中获取认证的用户信息,页面定义一个vue的data变量业接收,使用插值表达式在页面显示,页面加载时发送请求(vue created axios)

// 获取登陆用户的认证信息
User loginUser = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();

在jsp中获取登陆用户信息

${sessionScope.SPRING_SECURITY_CONTEXT.authentication.principal.username}

4:用户退出

调用/logout.do security帮我们做好了

【项目】健康项目day7总结相关推荐

  1. IBM沃森健康项目受挫 人工智能在医疗领域前景几何

    https://www.toutiao.com/a6655059819138908675/ 2019-02-07 22:56:00 近年来,人工智能和机器人已经开始在不少行业冲击人类劳动者.即便是被认 ...

  2. 项目管理有妙招,看懂你的项目健康状态和完整度

    项目管理的黑洞 有不少非软件工程科班专业的老板表示,真的不知道如何管理和把控软件项目的管理,因为那些编程的专业和编程语言他又不懂. 也有不少新晋的开发管理者,从以前负责系统的核心开发人员晋升到团队管理 ...

  3. Springboot毕设项目健康在线服务平台26iy0(java+VUE+Mybatis+Maven+Mysql)

    Springboot毕设项目健康在线服务平台26iy0(java+VUE+Mybatis+Maven+Mysql) 项目运行 环境配置: Jdk1.8 + Tomcat8.5 + Mysql + HB ...

  4. 【医疗健康项目】传智健康项目(三)

    第4章 预约管理-套餐管理 1. 图片存储方案 1.1 介绍 在实际开发中,我们会有很多处理不同功能的服务器.例如: 应用服务器:负责部署我们的应用 数据库服务器:运行我们的数据库 文件服务器:负责存 ...

  5. java健康检查代码_SpringBoot实现项目健康检查与监控

    Spring Boot 最主要的特性就是AutoConfig(自动配置),而对于我们这些使用者来说也就是各种starter, Spring Boot-Actuator 也提供了starter,为我们自 ...

  6. Spring Boot项目健康检测

    Spring Boot项目健康检测 Spring Boot是一种微服务架构,约定大于配置,免去了很多xml的配置.需要相关服务,我们可以在依赖中引入jar包即可.项目开发完成,我们需要验证项目是否正常 ...

  7. 【医疗健康项目】传智健康项目(一)

    第1章 项目概述和环境搭建 1. 项目概述 1.1 前言 传智健康管理系统是一款应用于健康管理机构的业务系统,实现健康管理机构工作内容可视化.会员管理专业化.健康评估数字化.健康干预流程化.知识库集成 ...

  8. Springboot毕设项目健康饮食搭配系统9m0ay(java+VUE+Mybatis+Maven+Mysql)

    Springboot毕设项目健康饮食搭配系统9m0ay(java+VUE+Mybatis+Maven+Mysql) 项目运行 环境配置: Jdk1.8 + Tomcat8.5 + Mysql + HB ...

  9. 【医疗健康项目】传智健康项目(四)

    第6章 移动端开发-体检预约 1. 移动端开发 1.1 移动端开发方式 随着移动互联网的兴起和手机的普及,目前移动端应用变得愈发重要,成为了各个商家的必争之地.例如,我们可以使用手机购物.支付.打车. ...

  10. 【医疗健康项目】传智健康项目(八)

    第13章 预约管理-JasperReports生成PDF报表 在前面的课程中我们完成了将运营数据导出到Excel文件的功能.在企业开发中,除了常见的Excel形式报表,还有PDF形式的报表.那么如何导 ...

最新文章

  1. 人大团队研究:面向文本生成,预训练模型进展梳理
  2. 青源 LIVE 预告 | McGill李岳Mila唐建团队新作:可迁移、可解释的单细胞RNA测序模型...
  3. 服务器端PHP多进程编程
  4. 就是肝!计算机基础知识总结与操作系统 PDF 下载
  5. iOS 后台返回json解析出现的null的解决办法
  6. 大到创业,小到做一份副业
  7. CString的基本用法
  8. 大学四年,电脑必备的三个宝藏工具软件
  9. 【7】PR音频及结合AU去除噪音【8】PR字幕运用
  10. mysql identity属性_Mysql中Identity 详细介绍
  11. ICASPP2022论文阅读记录2 - Transformer-S2A
  12. axios 的responseType 类型动态设置
  13. JGG | 河北大学杜会龙组综述植物泛基因组学研究
  14. 快递查询单号查询,分享简单好用查询技巧
  15. docker logs 说明
  16. 【技巧】如何以“只读方式”打开PPT文稿?附两个方法
  17. 【科普向】LaTeX简介(一篇极简的 LaTeX 介绍文章)
  18. !!!RFID原理及应用期末复习总结!!!少走弯路,直接满绩!
  19. 范德堡计算机科学硕士,范德堡大学计算机科学研究生怎么样?好不好
  20. 从Buck-Boost到Flyback

热门文章

  1. 平面波超声成像(Python代码+CIRS体模超声数据)
  2. 职教云python题和答案_智慧职教云课堂Python程序设计基础题目答案
  3. 2192-救基友记2
  4. 2022官方权威公布辐轮王土拨鼠全世界最值得买的碳纤维公路车
  5. 策略模式解决多重if-else
  6. java课程建设申报书,Java应用开发精品课程建设
  7. 中国硫代硫酸铵肥料行业市场供需与战略研究报告
  8. php获取上传文件的临时位置,PHP如何获取临时文件的目录路径
  9. MATLAB filter2
  10. Python与有趣的数学1