栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Spring Security——入门介绍

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Spring Security——入门介绍

目录
  • 1.初识Spring Security
    • 1.1.Spring Security概念
    • 1.2.权限管理概念
    • 1.3.完成权限管理需要三个对象
  • 2.Spring Security——简单入门
    • 2.1.创建web工程并导入jar包
    • 2.2.在web.xml中配置Spring Security过滤器链
    • 2.3.编写Spring Security配置文件spring-security.xml
    • 2.4.将spring-security.xml配置文件引入到applicationContext.xml中
    • 2.5.启动项目
  • 3.Spring Security——过滤器链
    • 3.1.Spring Security常用过滤器介绍
    • 3.2. Spring Security过滤器链加载原理
      • 3.2.1.DelegatingFilterProxy
      • 3.2.2.FilterChainProxy
      • 3.2.3.FilterChainProxy
  • 4.SpringSecurity——使用自定义认证页面
    • 4.1.指定认证页面配置信息
    • 4.2.SpringSecurity的csrf防护机制
      • 4.2.1.SpringSecurity中CsrfFilter过滤器说明
      • 4.2.2.处理403权限不足的异常
    • 4.3.SpringSecurity注销功能
  • 5.SpringSecurity——使用数据库数据完成认证
    • 5.1.认证流程分析
    • 5.2.初步实现认证功能
      • 5.2.1.自定义的UserService接口继承UserDetailsService
      • 5.2.2.编写loadUserByUsername业务
      • 5.2.3.在spring-security.xml中指定认证使用的业务对象
    • 5.3.加密认证
      • 5.3.1.在spring-security.xml提供加密对象
      • 5.3.2.修改认证方法
      • 5.3.3.修改添加用户的操作
      • 5.3.4.手动将数据库中用户密码改为加密后的密文
  • 6.设置用户状态
    • 6.1.源码分析
    • 6.2.判断认证用户的状态
  • 7.remember me
    • 7.1.记住我功能原理分析
    • 7.2.记住我功能页面代码
    • 7.3.开启remember me过滤器
    • 7.4.remember me安全性分析
    • 7.5.持久化remember me信息
    • 7.5.显示当前认证用户名
  • 8.授权操作
    • 8.1.前期准备工作
    • 8.2.动态展示菜单
    • 8.3.真正的授权操作
    • 8.4.权限不足异常处理

本文章的笔记整理来自黑马视频https://www.bilibili.com/video/BV1vt4y1i7zA,相关资料可以在该视频的评论区进行获取。该资料包括了提前准备好一个半成品的后台管理系统,而想要完善另一部分,就需要用到SpringSecurity。

1.初识Spring Security 1.1.Spring Security概念

Spring Security是spring采用AOP思想,基于servlet过滤器实现的安全框架。它提供了完善的认证机制和方法级的授权功能。是一款非常优秀的权限管理框架。其官方网址为:https://spring.io/projects/spring-security。

1.2.权限管理概念

(1)权限管理,一般指根据系统设置的安全规则或者安全策略,用户可以访问而且只能访问自己被授权的资源。权限管理几乎出现在任何系统里面,前提是需要有用户和密码认证的系统。
(2)在权限管理的概念中,有两个非常重要的名词:

认证通过用户名和密码成功登陆系统后,让系统得到当前用户的角色身份。
授权系统根据当前用户的角色,给其授予对应可以操作的权限资源。
1.3.完成权限管理需要三个对象
用户主要包含用户名,密码和当前用户的角色信息,可实现认证操作。
角色主要包含角色名称,角色描述和当前角色拥有的权限信息,可实现授权操作。
权限权限也可以称为菜单,主要包含当前权限名称,url地址等信息,可实现动态展示菜单。

注:这三个对象中,用户与角色是多对多的关系,角色与权限是多对多的关系,用户与权限没有直接关系,二者是通过角色来建立关联关系的。

2.Spring Security——简单入门 2.1.创建web工程并导入jar包

(1)web工程已事先准备好,不需要再创建。
(2)在pom.xml文件中导入Spring Security的相关依赖


    org.springframework.security
    spring-security-config
    5.1.5.RELEASE


    org.springframework.security
    spring-security-taglibs
    5.1.5.RELEASE

2.2.在web.xml中配置Spring Security过滤器链

    springSecurityFilterChain
    org.springframework.web.filter.DelegatingFilterProxy


    springSecurityFilterChain
    
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    try {
        //根据用户名做查询
        SysUser sysUser = userDao.findByName(username);
        if(sysUser==null){
            return null;
        }
        //authorities:当前用户所拥有的权限
        List authorities = new ArrayList<>();
        //获取当前拥有的角色
        List roles = sysUser.getRoles();
        for (SysRole role : roles) {
            authorities.add(new SimpleGrantedAuthority(role.getRoleName()));
        }
        
        UserDetails userDetails = new User(sysUser.getUsername(), "{noop}"+sysUser.getPassword(), authorities);
        return userDetails;
    }catch (Exception e){
        e.printStackTrace();
        //返回null时,SpringSecurity会认为认证失败!
        return null;
    }
}
5.2.3.在spring-security.xml中指定认证使用的业务对象

    
    

最后来进行测试:



需要注意的是,如果要修改用户的角色信息,也需要在相应页面添加SpringSecurity标签库、使用POST请求和动态携带token。

5.3.加密认证 5.3.1.在spring-security.xml提供加密对象



    
        
    

5.3.2.修改认证方法

即去掉loadUserByUsername()中的{noop}

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    try {
        //根据用户名做查询
        SysUser sysUser = userDao.findByName(username);
        if(sysUser==null){
            return null;
        }
        //authorities:当前用户所拥有的权限
        List authorities = new ArrayList<>();
        //获取当前拥有的角色
        List roles = sysUser.getRoles();
        for (SysRole role : roles) {
            authorities.add(new SimpleGrantedAuthority(role.getRoleName()));
        }
        
        UserDetails userDetails = new User(sysUser.getUsername(), sysUser.getPassword(), authorities);
        return userDetails;
    }catch (Exception e){
        e.printStackTrace();
        //认证失败!
        return null;
    }
}
5.3.3.修改添加用户的操作

即对用户密码进行加密操作后,再存入数据库

//注入加密对象
@Autowired
private BCryptPasswordEncoder passwordEncoder;

@Override
public void save(SysUser user) {
    //对用户密码进行加密操作
    user.setPassword(passwordEncoder.encode(user.getPassword()));
    userDao.save(user);
}
5.3.4.手动将数据库中用户密码改为加密后的密文

可以编写一个测试用例,获取明文密码加密后的密文,最后再将其写入数据库。

6.设置用户状态 6.1.源码分析

在用户认证业务里,SpringSecurity封装User对象时,选择了三个构造参数的构造方法,其实还有另一个构造方法:

public User(String username, String password, boolean enabled, boolean accountNonExpired,
			boolean credentialsNonExpired, boolean accountNonLocked, Collection
authorities) {
	if (username != null && !"".equals(username) && password != null) {
		this.username = username;
		this.password = password;
		this.enabled = enabled;
		this.accountNonExpired = accountNonExpired;
		this.credentialsNonExpired = credentialsNonExpired;
		this.accountNonLocked = accountNonLocked;
		this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities));
	} else {
		throw new IllegalArgumentException("Cannot pass null or empty values to constructor");
	}
}

可以看到,这个构造方法里多了四个布尔类型的构造参数,其实我们使用的三个构造参数的构造方法里这四个布尔值默认都被赋值为了true,这四个布尔值的具体含义如下:

boolean enabled是否可用
boolean accountNonExpired账户是否失效
boolean credentialsNonExpired秘密是否失效
boolean accountNonLocked账户是否锁定
6.2.判断认证用户的状态

上面的四个参数必须同时为true时,认证才通过,为了节省时间,这里只用第一个布尔值做测试,修改后的认证业务代码如下:

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    try {
        //根据用户名做查询
        SysUser sysUser = userDao.findByName(username);
        if(sysUser==null){
            return null;
        }
        //authorities:当前用户所拥有的权限
        List authorities = new ArrayList<>();
        //获取当前拥有的角色
        List roles = sysUser.getRoles();
        for (SysRole role : roles) {
            authorities.add(new SimpleGrantedAuthority(role.getRoleName()));
        }
        
        UserDetails userDetails = new User(sysUser.getUsername(),
                sysUser.getPassword(),
                sysUser.getStatus()==1,
                true,
                true,
                true,
                authorities);
        return userDetails;
    }catch (Exception e){
        e.printStackTrace();
        //认证失败!
        return null;
    }
}

此刻,只有用户状态为1的用户才能成功通过认证!(用户表结构如下图所示)

7.remember me 7.1.记住我功能原理分析
public abstract class AbstractRememberMeServices implements RememberMeServices, InitializingBean, LogoutHandler { 
	public final void loginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) { 
		// 判断是否勾选记住我 
		// 注意:这里this.parameter点进去是上面的private String parameter = "remember-me"; 
		if (!this.rememberMeRequested(request, this.parameter)) { 
			this.logger.debug("Remember-me login not requested."); } 
		else { 
			//若勾选就调用onLoginSuccess方法 
			this.onLoginSuccess(request, response, successfulAuthentication); 
		} 
	} 
}

再点进去上面if判断中的rememberMeRequested方法,还在当前类中:

protected boolean rememberMeRequested(HttpServletRequest request, String parameter) { 
	if (this.alwaysRemember) {
		return true; 
	} else { 
		// 从上面的字parameter的值为"remember-me" 
		// 也就是说,此功能提交的属性名必须为"remember-me" 
		String paramValue = request.getParameter(parameter); 
		// 这里我们看到属性值可以为:true,on,yes,1。 
		if (paramValue != null && (paramValue.equalsIgnoreCase("true") || paramValue.equalsIgnoreCase("on") || paramValue.equalsIgnoreCase("yes") || paramValue.equals("1"))) { 
		//满足上面条件才能返回true 
		return true; 
		} else { 
		if (this.logger.isDebugEnabled()) { 
			this.logger.debug("Did not send remember-me cookie (principal did not set parameter '" + parameter + "')"); }
			return false; 
		} 
	} 
}

如果上面方法返回true,就表示页面勾选了记住我选项了。继续顺着调用的方法找到PersistentTokenbasedRememberMeServices的onLoginSuccess方法:

public class PersistentTokenbasedRememberMeServices extends AbstractRememberMeServices {
	protected void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) {
		// 获取用户名
        String username = successfulAuthentication.getName();
        this.logger.debug("Creating new persistent login for user " + username);
        //创建记住我的token
        PersistentRememberMeToken persistentToken = new PersistentRememberMeToken(username, this.generateSeriesData(), this.generateTokenData(), new Date());

        try {
        	//将token持久化到数据库
            this.tokenRepository.createNewToken(persistentToken);
            //将token写入到浏览器的cookie中
            this.addcookie(persistentToken, request, response);
        } catch (Exception var7) {
            this.logger.error("Failed to save persistent token ", var7);
        }
    }
}
7.2.记住我功能页面代码
7.3.开启remember me过滤器

在spring-security.xml中开启remember me过滤器:


说明:RememberMeAuthenticationFilter中功能非常简单,会在打开浏览器时,自动判断是否认证,如果没有则调用autoLogin进行自动认证。

在登录时选中记住复选框,那么在token的有效存储时间内,如果关闭浏览器并再次打开时,就可以不用登录,直接输入后台主页面地址进行访问。

7.4.remember me安全性分析

(1)记住我功能方便是大家看得见的,但是安全性却令人担忧。因为cookie毕竟是保存在客户端的,很容易盗取,而且cookie的值还与用户名、密码这些敏感数据相关,虽然加密了,但是将敏感信息存在客户端,还是不太安全。那么这就要提醒喜欢使用此功能的,用完网站要及时手动退出登录,清空认证信息。
(2)此外,SpringSecurity还提供了remember me的另一种相对更安全的实现机制 ,即在客户端的cookie中,仅保存一个无意义的加密串(与用户名、密码等敏感数据无关),然后在数据库中保存该加密串-用户信息的对应关系,自动登录时,用cookie中的加密串,到数据库中验证,如果通过,自动登录才算通过。

7.5.持久化remember me信息

(1)创建一张名为persistent_logins表,注意这张表的名称和字段都是官方指定的,不要修改。

CREATE TABLE `persistent_logins` ( 
	`username` varchar(64) NOT NULL, 
	`series` varchar(64) NOT NULL, 
	`token` varchar(64) NOT NULL, 
	`last_used` timestamp NOT NULL, 
	PRIMARY KEY (`series`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8

(2)在spring-security.xml中对之前的配置进行修改:

 

(3)再次测试后,发现该表中多了一条记录,即remember me的相关信息被持久化在数据库中,相对来说更加安全。

7.5.显示当前认证用户名

在header.jsp(先要添加Spring Security标签库)中找到页面头部最右侧图片处添加如下信息:

 
	 
 

8.授权操作

此处的授权操作是指对于拥有不同权限的用户,进入后台管理系统所能进行的操作(或者说到的页面)是不一样的。例如现在在数据库表sys_user中有两个模拟用户xiaoming和xiaoma,xiaoming在后台能进行产品管理,但不能进行订单管理,而xiaoma则正好相反,现在就要实现不同的用户进入后台系统所能看到的功能菜单不一样。

8.1.前期准备工作

为了模拟授权操作,现临时编写两个业务功能,其处理器代码如下:

//ProductController 
@Controller 
@RequestMapping("/product") 
public class ProductController { 
	@RequestMapping("/findAll") 
	public String findAll(){ 
		return "product-list"; 
	} 
}
	
//OrderController 
@Controller 
@RequestMapping("/order") 
public class OrderController {
	@RequestMapping("/findAll") 
	public String findAll(){ 
		return "order-list"; 
	} 
}

aside.jsp页面中相应的功能菜单:


8.2.动态展示菜单

(1)先在数据库中为用户xiaoming和xiaoma准备好相关的角色



(2)先在aside.jsp中添加Spring Security的标签库,然后对产品管理、订单管理这两个功能菜单进行如下修改:

    <%--只有拥有ROLE_PRODUCT或ROLE_ADMIN角色的用户才能看见产品管理这一功能菜单--%>
  • 产品管理
  • <%--只有拥有ROLE_ORDER或ROLE_ADMIN角色的用户才能看见订单管理这一功能菜单--%>
  • 订单管理

(3)分别使用xiaoming和xiaoma作为用户名进行登录


虽然从功能菜单的显示效果上看起来,好像已经对xiaoming和xiaoma授权成功了,但是实际上并没有。例如可以试试直接去访问产品的http请求地址,此时发现原本不能进行产品管理的xiaoma却能直接操作!

所以说现xiaoma其实是可以操作产品模块的,只是系统没有把产品功能展示给xiaoma而已,即页面动态菜单的展示只是为了用户体验,并未真正控制权限!

8.3.真正的授权操作

说明:SpringSecurity可以通过注解的方式来控制类或者方法的访问权限。注解需要对应的注解支持,若注解放在controller类中,对应注解支持应该放在mvc配置文件中,因为controller类是有mvc配置文件扫描并创建的,同理,注解放在service类中,对应注解支持应该放在spring配置文件中。由于我们现在是模拟业务操作,并没有service业务代码,所以就把注解放在controller类中了。

(1)开启授权的注解支持
在SpringMVC的配置文件spring-mvc.xml中开启授权的注解支持


(2)在注解支持对应类或者方法上添加注解

//ProductController
//表示当前用户需要ROLE_PRODUCT或者ROLE_ADMIN才能访问该方法
//@Secured({"ROLE_PRODUCT","ROLE_ADMIN"})//springSecurity内部制定的注解
//@RolesAllowed({"ROLE_PRODUCT","ROLE_ADMIN"})//jsr250注解
@PreAuthorize("hasAnyAuthority('ROLE_PRODUCT','ROLE_ADMIN')")//spring的el表达式注解
@RequestMapping("/findAll")
public String findAll(){
    return "product-list";
}

//OrderController
//表示当前用户需要ROLE_ORDER或者ROLE_ADMIN角色才能访问该方法
@Secured({"ROLE_ORDER","ROLE_ADMIN"})
@RequestMapping("/findAll")
public String findAll(){
    return "order-list";
}

此时用xiaoma作为用户名进行登录,并且再直接去访问产品的http请求地址,会出现权限不足的提示!这说明授权真正地成功了。

8.4.权限不足异常处理

通过上面的例子,大家也发现了每次权限不足时都出现了403页面,这对用户的观感体验是非常不友好的,所以应该想办法处理一下异常,即跳转到一个相对来说对用户友好易懂的页面。

(1)异常处理流程图:

(2)三种常用的处理异常的方式
方式一:在spring-security.xml配置文件中处理


该方式的缺点在于只能处理403权限不足的这种异常,而不能处理其它类型的异常!(不推荐使用)

方式二:在web.xml中进行处理


    403
    /403.jsp
 



    404
    /404.jsp
 

方式三:编写异常处理器(推荐使用)

package com.itheima.controller.advice;

import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.acls.model.NotFoundException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class HandlerControllerAdvice{
    
    //针对不同的异常类型,跳转到不同的页面
    @ExceptionHandler(AccessDeniedException.class)
    public String handlerException(){
        return "redirect:/403.jsp";
    }
    
    @ExceptionHandler(NotFoundException.class)
    public String notFoundException(){
        return "redirect:/404.jsp";
    }
    
    @ExceptionHandler(RuntimeException.class)
    public String runtimeHandlerException(){
        return "redirect:/500.jsp";
    }
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/294292.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号