博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
权限控制之Spirng Security框架
阅读量:2056 次
发布时间:2019-04-28

本文共 11404 字,大约阅读时间需要 38 分钟。

1. Spring Security简介

Spring Security是 Spring提供的安全认证服务的框架。 使用Spring Security可以帮助我们来简化认证和授权的过程。官网:,Spring Security是基于Filter(过滤器实现)

在这里插入图片描述

对应的maven坐标:

org.springframework.security
spring-security-web
5.0.5.RELEASE
org.springframework.security
spring-security-config
5.0.5.RELEASE

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

3.4 Spring Security入门案例

3.4.1 工程搭建

创建maven工程,打包方式为war,为了方便起见我们可以让入门案例工程依赖health_interface,这样相关的依赖都继承过来了,实际上。Spring Security是基于Filter(过滤器实现)

pom.xml

4.0.0
com.itheiheihei
springsecuritydemo
1.0-SNAPSHOT
war
springsecuritydemo Maven Webapp
http://www.example.com
UTF-8
1.8
1.8
com.itheiheihei
health_interface
1.0-SNAPSHOT
org.apache.tomcat.maven
tomcat7-maven-plugin
85
/

提供index.html页面,内容为hello Spring Security!!

3.4.2 配置web.xml

在web.xml中主要配置SpringMVC的DispatcherServlet和用于整合第三方框架的DelegatingFilterProxy,用于整合Spring Security。Spring Security是基于Filter(过滤器实现)

springSecurityFilterChain
org.springframework.web.filter.DelegatingFilterProxy
springSecurityFilterChain
/*
springmvc
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:spring-security.xml
1
springmvc
*.do

3.4.3 配置spring-security.xml

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

Spring Security是基于Filter(过滤器实现)

补充:hasAndRole等方法本质是一样的(调用同一方法)

3.5 对入门案例改进

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

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

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

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

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

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

    本章需要对这些问题进行改进。Spring Security是基于Filter(过滤器实现)

3.5.1 配置可匿名访问的资源(建议叫放行)

第一步:在项目中创建pages目录,在pages目录中创建a.html和b.html

第二步:在spring-security.xml文件中配置,指定哪些资源可以匿名访问,但是不能够获取匿名账号

注意:/**是放行下面的所有资源

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

<security:http auto-config="true" use-expressions="true">中的<security:intercept-url pattern="/路径" access="isAnonymous()"/>具有同一效果,但是可以获取到匿名账号

3.5.2 使用指定的登录页面

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

    
登录
username:
password:

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

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

注意:需要在<security:http></security:http>中进行配置

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

注意:也是在<security:http></security:http>中进行配置,这是进行跨越请求的放行

3.5.3 从数据库查询用户信息

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

实现类代码:

package com.itheiheihei.service;import com.itheiheihei.pojo.User;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 java.util.*;/** * @author 嘿嘿嘿1212 * @version 1.0 * @date 2019/10/23 11:17 */public class SpringSecurityUserService implements UserDetailsService {
public static Map
map = new HashMap<>(); static {
//模拟数据库信息 com.itheiheihei.pojo.User user1 = new com.itheiheihei.pojo.User(); user1.setUsername("admin"); user1.setPassword("admin"); com.itheiheihei.pojo.User user2 = new com.itheiheihei.pojo.User(); user2.setUsername("xiaoming"); user2.setPassword("1234"); map.put(user1.getUsername(), user1); map.put(user2.getUsername(), user2); } /** * 根据用户名加载用户信息 * * @param username * @return * @throws UsernameNotFoundException */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("用户输入的用户名为" + username); //判空 if (username == null) {
return null; } else {
//模拟查询数据库 User userInDb = map.get(username); //查询数据库是否为空 if (userInDb == null) {
return null; } //模拟数据库中的密码,需要查询数据库 String passwordInDb = "{noop}" + userInDb.getPassword(); List
list = new ArrayList<>(); //授权,需要改成查询数据库动态获取用户拥有的权限和角色 list.add(new SimpleGrantedAuthority("permission_a")); list.add(new SimpleGrantedAuthority("permission_b")); //根据用户授予角色 if ("admin".equals(username)) {
//授予角色 list.add(new SimpleGrantedAuthority("ROLE_ADMIN")); } org.springframework.security.core.userdetails.User securityUser = new org.springframework.security.core.userdetails.User(username, passwordInDb, list); return securityUser; } }}

spring-security.xml:

注意:处理类需要实现UserDetailsService接口,并在认证管理器中配置<security:authentication-provider user-service-ref="userService">

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

3.5.4 对密码进行加密

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

常见的密码加密方式有:

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

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

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

    加密后的格式一般为:

    $2a$10$/bTVvqqlH9UiE0ZJZ7N2Me3RIgUCdgMheyTgV0B4cMCSokPa.6oCa

    加密后字符串的长度为固定的60位。其中:$是分割符,无意义;2a是bcrypt加密版本号;10是cost的值;而后的前22位是salt值;再然后的字符串就是密码的密文了。

实现步骤:

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

第二步:修改UserService实现类

package com.itheiheihei.service;import com.itheiheihei.pojo.User;import org.springframework.beans.factory.annotation.Autowired;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.security.crypto.bcrypt.BCryptPasswordEncoder;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;/** * @author 嘿嘿嘿1212 * @version 1.0 * @date 2019/10/23 11:17 */public class SpringSecurityUserService2 implements UserDetailsService {
//模拟数据库中的用户数据 public static Map
map = new HashMap<>(); @Autowired private BCryptPasswordEncoder bCryptPasswordEncoder; public void initUserData() {
User user1 = new User(); user1.setUsername("admin"); user1.setPassword(bCryptPasswordEncoder.encode("admin")); User user2 = new User(); user2.setUsername("xiaoming"); user2.setPassword(bCryptPasswordEncoder.encode("1234")); map.put(user1.getUsername(), user1); map.put(user2.getUsername(), user2); } /** * 根据用户名加载用户信息 * @param username * @return * @throws UsernameNotFoundException */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
initUserData(); System.out.println("用户输入的用户名为" + username); if (username == null) {
return null; } else {
//查询数据库 User user = map.get(username); //根据用户名没有查询到用户 if(user==null){
return null; } //模拟获取数据库中的密码 String password = user.getPassword(); List
list = new ArrayList<>(); //授权,需要改为查询数据库动态获取用户拥有的权限和角色 list.add(new SimpleGrantedAuthority("permission_a")); list.add(new SimpleGrantedAuthority("permission_b")); if ("admin".equals(username)) {
//授予角色 list.add(new SimpleGrantedAuthority("ROLE_ADMIN")); } org.springframework.security.core.userdetails.User securityUser = new org.springframework.security.core.userdetails.User(username, password, list); return securityUser; } }}

注意:返回Security的User时,在5.0.0版本以上,建议在密码前添加{bcrypt}

User securityUser =  new oUser(username, "{bcrypt}"+password, list);

可能不添加{bcrypt}会出现报错情况

3.5.5 配置多种校验规则

为了测试方便,首先在项目中创建a.html、b.html、c.html、d.html几个页面修改spring-security.xml文件:

注意:Spring security框架根据配置顺序给予URL添加访问权限,如果同一URL被赋予两次,将以先赋予的权限为准,在配置文件中表现为后赋予URL权限的<security:intercept-url>无效

3.5.6 注解方式权限控制

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

实现步骤:

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

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

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

package com.itheiheihei.controller;import org.springframework.security.access.prepost.PreAuthorize;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;/** * @author 嘿嘿嘿1212 * @version 1.0 * @date 2019/10/23 18:51 */@RestController@RequestMapping("/hello")public class HelloController {
/** * 表示拥有add的权限,才能够调用当前方法 * @return */ @RequestMapping("/add") @PreAuthorize("hasAuthority('add')") public String add() {
return "success"; } /** * 表示必须拥有ADMIN角色才能够调用该方法 * @return */ @RequestMapping("/delete") @PreAuthorize("hasRole('ROLE_ADMIN')") public String delete() {
return "delete"; }}

注意:如hasRole等授予角色或权限中带有And的方法,那么要求拥有其中角色组或权限组中一项权限或角色的用户即可访问

3.5.7 退出登录

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

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

Security 请求跨域问题

在其他域名的请求访问时,需要放开权限时,在请求上添加@CrossOrigin,并指定开放的域名,如果不写,将会默认为*即开放访问.

@CrossOrigin(value={
"域名"})@RequsetMaping("/被请求的URL")public void add(){
....}

转载地址:http://dnilf.baihongyu.com/

你可能感兴趣的文章
Leetcode C++ 《拓扑排序-1》20200626 207.课程表
查看>>
Go语言学习Part1:包、变量和函数
查看>>
Go语言学习Part2:流程控制语句:for、if、else、switch 和 defer
查看>>
Go语言学习Part3:struct、slice和映射
查看>>
Go语言学习Part4-1:方法和接口
查看>>
Leetcode Go 《精选TOP面试题》20200628 69.x的平方根
查看>>
Leetcode C++ 剑指 Offer 09. 用两个栈实现队列
查看>>
Leetcode C++《每日一题》20200707 112. 路径总和
查看>>
云原生 第十一章 应用健康
查看>>
Leetcode C++ 《第202场周赛》
查看>>
云原生 第十二章 可观测性:监控与日志
查看>>
Leetcode C++ 《第203场周赛》
查看>>
云原生 第十三章 Kubernetes网络概念及策略控制
查看>>
《redis设计与实现》 第一部分:数据结构与对象 || 读书笔记
查看>>
《redis设计与实现》 第二部分(第9-11章):单机数据库的实现
查看>>
算法工程师 面经2019年5月
查看>>
搜索架构师 一面面经2019年6月
查看>>
稻草人手记
查看>>
第一次kaggle比赛 回顾篇
查看>>
leetcode 50. Pow(x, n)
查看>>