Spring Boot Security 学习笔记-根据登陆人动态配置权限-密码加密验证

本文详细介绍如何使用 Spring Security 实现用户认证及授权,包括权限配置、密码加密与验证、前后端权限控制等,并附带完整的代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、Spring Security 基本介绍

本文举例可以根据登陆用户动态登陆和配置权限-假装写死的数据是从数据库取出的即可,因为为了便于陈述没有实际从数据库取。

比对Spring Boot 实现最简单的 Security

Spring Security 会对指定路径进行过滤,包含用户名密码验证,以及权限的赋予,访问路径的拦截。在Spring Boot 的实现中,这些功能都是基于对一些类、接口或者方法的覆盖重写来实现的。

总的来说应用场景可以分为两个:

·用户-权限-前台展示那些内容

·用户-权限-后台那些url对该用户开放



具体场景举例:

·用户登陆访问该链接需要某个特定权限

·用户访问该链接需要某个权限,但是这个权限是任意的

·用户访问该链接不需要任何权限

·用户登陆密码校验,这里细分为后台被校验的密码是明文和被加密后的密文两种情况

·前台根据当前用户具有的权限进行页面元素展示权限的判断



2、使用自动生成代码

http://blog.youkuaiyun.com/bestcxx/article/details/78028793


3、Maven 补全

自动生成代码勾选了Thymeleaf,但是在实际操作中,发现实际上少了,这样前台的一些权限相关的控制功能会失效

<dependency>
			<groupId>org.thymeleaf.extras</groupId>
			<artifactId>thymeleaf-extras-springsecurity4</artifactId>
		</dependency>

现在我依旧把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.bestcxx.stu</groupId>
	<artifactId>springbootsecuritydb</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>war</packaging>

	<name>springbootsecuritydb</name>
	<description>Spring Boot 结合 Spring Security </description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.7.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
		
		<dependency>
			<groupId>org.thymeleaf.extras</groupId>
			<artifactId>thymeleaf-extras-springsecurity4</artifactId>
		</dependency>
		
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-tomcat</artifactId>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-test</artifactId>
			<scope>test</scope>
		</dependency>
		
		
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>


</project>

4、代码结构



5、com.bestcxx.stu.springbootsecuritydb 不做修改,启动的main方法所在类


6、com.bestcxx.stu.springbootsecuritydb.bean 自定义的实体-对应数据库表

(强调,本案例说是链接数据库,但是并没有真的从数据库取数据,出于简明叙述的考虑,数据是写死的,下面你就看到了)


Role.java

package com.bestcxx.stu.springbootsecuritydb.bean;

import java.io.Serializable;

/**
 * 表示 用户名为 userName 的用户的权限实体
 * @author Administrator
 */
public class Role implements Serializable{

	/**Role 主键 id*/
	private Long id;
	
	/**关联的com.bestcxx.stu.springbootsecuritydb.bean.User 的userName*/
	private String userName;
	
	/**角色名称*/
	private String roleName;

	public Long getId() {
		return id;
	}

	public String getUserName() {
		return userName;
	}

	public String getRoleName() {
		return roleName;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public void setUserName(String userName) {
		this.userName = userName;
	}

	public void setRoleName(String roleName) {
		this.roleName = roleName;
	}
	
}

User.java

package com.bestcxx.stu.springbootsecuritydb.bean;

import java.io.Serializable;

public class User implements Serializable{

	/**
	 * 和实体业务对应的用户实体
	 */
	private static final long serialVersionUID = -7549254798751179167L;
	/**用户名*/
	private String userName;
	
	/**密码*/
	private String password;
	
	/**年龄*/
	private int age;
	
	
	public String getUserName() {
		return userName;
	}
	public String getPassword() {
		return password;
	}
	public void setUserName(String userName) {
		this.userName = userName;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	
	

}

7、com.bestcxx.stu.springbootsecuritydb.common.util 工具类

package com.bestcxx.stu.springbootsecuritydb.common.util;

import java.security.MessageDigest;

/**
 * MD5 工具类-建议添油加醋的对入参 str 改造一下
 * @author Administrator
 *
 */
public class MD5Util{

	//工具类不允许被实例化
	private MD5Util() throws Exception {
		throw new Exception("异常");
	}
	public static String encode(String str) {
		MessageDigest md5 = null;
		try {
			md5 = MessageDigest.getInstance("MD5");
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		char[] charArray = str.toCharArray();
		byte[] byteArray = new byte[charArray.length];

		for (int i = 0; i < charArray.length; i++)
			byteArray[i] = (byte) charArray[i];
		byte[] md5Bytes = md5.digest(byteArray);
		StringBuffer hexValue = new StringBuffer();
		for (int i = 0; i < md5Bytes.length; i++) {
			int val = ((int) md5Bytes[i]) & 0xff;
			if (val < 16) {
				hexValue.append("0");
			}

			hexValue.append(Integer.toHexString(val));
		}
		return hexValue.toString();
	}

	public static void main(String[] args) {
		System.out.println(encode("admin"));
	}
}

8、com.bestcxx.stu.springbootsecuritydb.security.bean 特殊的实体-登陆人和权限实体

这个实体类很神奇,它需要最终实现org.springframework.security.core.userdetails.UserDetails 接口,以打包你需要的用户登陆信息和权限

SecurityUser.java

package com.bestcxx.stu.springbootsecuritydb.security.bean;

import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;

/**
 * Security 安全校验实体-用于封转登陆用户的身份信息和权限信息
 * 实质上是对 org.springframework.security.core.userdetails.UserDetails 接口的实现
 * org.springframework.security.core.userdetails.User 提供了构造方法,便于我们业务用户实体和Security 校验身份的实体分离
 * @author Administrator
 *
 */
public class SecurityUser extends User {
	
	private static final long serialVersionUID = -254576396255401176L;

	//这里可以增加自定义参数
	/**
	 * private int age;
	 * private int number;
	 * eg.
	 */
	/**年龄*/
	private int age;
	
	/**
	 * 有参构造方法,可以扩充参数
	 * @param username      基本参数
	 * @param password      基本参数
	 * @param authorities   基本参数-表示登陆权限的字符串集合-比如ROLE_ADMIN,可以自定义
	 */
	public SecurityUser(String username, String password, Collection<? extends GrantedAuthority> authorities,int age) {
		super(username, password, authorities);
		this.age=age;
	}

	
	
	/**
	 * 对于新增的自定义参数
	 * 赋值操作在有参构造方法中
	 * 取值操作需要提供get方法
	 */
	public int getAge() {
		return age;
	}
	
}

9、com.bestcxx.stu.springbootsecuritydb.security.service  登陆配置用户信息的service

该类实现 org.springframework.security.core.userdetails.UserDetailsService ,覆盖重写了 loadUserByUsername

具体看类内部的注释吧

CustomUserService.java

我们赋予了两个权限  ROLE_ADMIN和ROLE_COMMON 

用于测试前段显示和后端url访问权限

package com.bestcxx.stu.springbootsecuritydb.security.service;

import java.util.ArrayList;
import java.util.List;

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 com.bestcxx.stu.springbootsecuritydb.bean.Role;
import com.bestcxx.stu.springbootsecuritydb.bean.User;
import com.bestcxx.stu.springbootsecuritydb.security.bean.SecurityUser;

/**
 * 本类需要实现 org.springframework.security.core.userdetails.UserDetailsService 接口
 * 然后覆盖重写 loadUserByUsername(String userName) 方法
 * 在该方法内部,需要添加 userName,passWord,权限集合,其他参数 到我们已经处理好的com.bestcxx.stu.springbootsecuritydb.security.bean.SecurityUser
 * 然后返回即可-没有密码校验?是的,密码校验不在这个地方,已经被封装好了,但是-数据库密码一般都是加密的,前端信息传递到这还是明文形式,
 * 所以肯定有办法覆盖重写密码校验的方法,这里先卖个关子,先让我们的程序在密码明文校验的情况下顺利运行吧
 * 好吧,就是在 com.bestcxx.stu.springbootsecuritydb.config.WebSecurityConfig 类中配置了怎么校验密码
 * 
 * 本例没有连接数据库,但是会按照连接数据库的流程进行讲解
 * @author wj
 *
 */
public class CustomUserService implements UserDetailsService {

	/**
	 * 用户登陆校验
	 * 覆盖重写了 UserDetailsService.loadUserByUsername,需返回 配置了权限的UserDetails的子类对象,增加权限
	 * 作为登陆用户权限配置的依据
	 */
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		System.out.println("录入的username值为:"+username);
		
		//1、根据 username 从数据库获取用户信息
		//com.bestcxx.stu.springbootsecuritydb.bean.User 对应数据库用户信息
		User user=new User();
		user.setUserName(username);
		//user.setPassword("admin");//admin md5加密 21232f297a57a5a743894a0e4a801fc3
		user.setPassword("21232f297a57a5a743894a0e4a801fc3");//admin md5加密 21232f297a57a5a743894a0e4a801fc3
		user.setAge(20);
		
				//理论上,如果真的是从数据库查询,这里需要做一个查询判空
				if(user==null){
					throw new UsernameNotFoundException("username 不存在");
				}
		
		//2、根据 user 获取权限
		//com.bestcxx.stu.springbootsecuritydb.bean.Role 对应数据库 用户权限信息
		List<Role> roleList=new ArrayList<Role>();
		
		List<GrantedAuthority> authorities=new ArrayList<GrantedAuthority>();
		
		//遍历 roleList 将 相应的权限放置到 authorities 中
		for(Role role:roleList){
			GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(role.getRoleName());//权限实体
			authorities.add(grantedAuthority);//增加到权限队列中
		}
		
		//由于我们这里并没有从数据库中获取Role 数据,所以这里写一个默认值,根据规则,ROLE_开头,养成良好的命名规范
		//这里我们规定了用户权限ROLE_ADMIN 这样在页面 home.html中 sec:authorize="hasRole('ROLE_ADMIN')" 就限定了拥有该权限的才可以访问
		authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
		
		//这里我们规定了用户权限ROLE_COMMON 这样在
		authorities.add(new SimpleGrantedAuthority("ROLE_COMMON"));
		
		
		return new SecurityUser(user.getUserName(),user.getPassword(),authorities,user.getAge());
	}

}

10、com.bestcxx.stu.springbootsecuritydb.config 对于安全的控制-用户信息和访问权限的配置

前面把基础工作都做好了,这里是集大成者,详情看类内部注释

WebSecurityConfig.java

package com.beWebSecurityConfigstcxx.stu.springbootsecuritydb.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;

import com.bestcxx.stu.springbootsecuritydb.common.util.MD5Util;
import com.bestcxx.stu.springbootsecuritydb.security.service.CustomUserService;

/**
 * 注意  protected void configure(AuthenticationManagerBuilder auth) throws Exception { 方法中
 * 密码校验具有加密和不加密两种情况,所谓加密是值,前台传递来的是明文,后台被比较的-从数据库取出来的密码是明文加密后存储的,
 * 这样需要将前台明文密码加密后和数据库存储的密码进行比较,
 * 需要结合 com.bestcxx.stu.springbootsecuritydb.security.service.CustomUserService 类中的注释进行理解 
 * @author wj
 *
 */
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

	@Bean
	UserDetailsService customUserService() {
		return new CustomUserService();
	}

	/**
	 * 用户登陆校验
	 * 调用了customUserService(),内部覆盖重写了 UserDetailsService.loadUserByUsername,需返回 配置了权限的UserDetails的子类对象
	 * 作为登陆用户权限配置的依据
	 */
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		/**对于数据库密码不加密的情况*/
		//auth.userDetailsService(customUserService()); 
		
		/**对于数据库密码加密的情况*/
		auth.userDetailsService(customUserService()).passwordEncoder(new PasswordEncoder(){
			//rawPassword 前台传递来的 password
			//encodedPassword 后台计算的 password
			@Override
			public String encode(CharSequence rawPassword) {
				return MD5Util.encode((String)rawPassword);
			}

			@Override
			public boolean matches(CharSequence rawPassword, String encodedPassword) {
				 return encodedPassword.equals(MD5Util.encode((String)rawPassword));
			}
			
		});
	}

	/**
	 * 配置 特殊权限-特殊路径
	 * 配置 任意权限-剩余路径
	 * 配置 登陆页-用户名、密码-登陆失败页-不需要权限
	 */
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.authorizeRequests()
		.antMatchers("/common/**").hasRole("COMMON")//需要权限ROLE_COMMON 才可以访问的路径   <a th:href="@{/common/test}">去test.html</a>
		.anyRequest().authenticated() // 只有具有任意的某个权限就可以访问其他访问-没有权限还是无法访问的
		.and()
		.formLogin()//对于form表单登陆
		//.passwordParameter("a").usernameParameter("b")//如果你前台登陆的form表单登录名和密码不是username,password,那么就配置本行修改你需要的名字
		.loginPage("/login")//未登陆的情况下,默认跳转的页面
		.failureUrl("/login?error").permitAll() //如果登陆失败,跳转的url
		.and().logout().permitAll(); // 允许任何请求(不管有没有权限以及拥有何种权限)登出
		
	}
	
	

}

11、com.bestcxx.stu.springbootsecuritydb.security.controller 控制器类

HomeController.java

package com.bestcxx.stu.springbootsecuritydb.security.controller;

import java.util.HashMap;

import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;


@Controller
public class HomeController {
	
	@RequestMapping(value={"/","/login"})
	public ModelAndView index(Model model){
		if (!SecurityContextHolder.getContext().getAuthentication().getName().equals("anonymousUser")){ //已登录状态访问 则自动跳到系统首页
			//虽然这里什么也没有,但是其实,Security 用户登陆的检测已经在起作用了
			HashMap<String,Object> msg=new HashMap<String,Object>();
			msg.put("title", "无需特殊权限-测试标题");
			msg.put("content", "无需特殊权限-测试内容");
			msg.put("etraInfo", "额外信息,只对具有 ROLE_ADMIN 权限的显示");
			model.addAttribute("msg", msg);
			return new ModelAndView("home").addObject(msg);
        }
		return new ModelAndView("login");
		
	}
	
	@RequestMapping("/common/test")
	public String test(){
		return "common/test";
	}

}

12、关键内容已经完成了,css文件(具体内容略)



13、html页面


login.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta content="text/html;charset=UTF-8"/>
<title>登录页面</title>
<link rel="stylesheet" th:href="@{css/bootstrap.min.css}"/>
<style type="text/css">
	body {
  padding-top: 50px;
}
.starter-template {
  padding: 40px 15px;
  text-align: center;
}
</style>
</head>
<body>
	
	 <nav class="navbar navbar-inverse navbar-fixed-top">
      <div class="container">
        <div class="navbar-header">
          <a class="navbar-brand" href="#">Spring Security演示</a>
        </div>
        <div id="navbar" class="collapse navbar-collapse">
          <ul class="nav navbar-nav">
           <li><a th:href="@{/}"> 首页 </a></li>
           
          </ul>
        </div><!--/.nav-collapse -->
      </div>
    </nav>
     <div class="container">

      <div class="starter-template">
       <p th:if="${param.logout}" class="bg-warning">已成功注销</p><!-- 1 -->
			<p th:if="${param.error}" class="bg-danger">有错误,请重试</p> <!-- 2 -->
			<h2>使用账号密码登录</h2>
			<form name="form" th:action="@{/login}" action="/login" method="POST"> <!-- 3 -->
				<div class="form-group">
					<label for="username">账号</label>
					<input type="text" class="form-control" name="username" value="" placeholder="账号" />
				</div>
				<div class="form-group">
					<label for="password">密码</label>
					<input type="password" class="form-control" name="password" placeholder="密码" />
				</div>
				<input type="submit" id="login" value="Login" class="btn btn-primary" />
			</form>
      </div>

    </div>
		
</body>
</html>

home.html   

后台配置可以显示的选项,访问链接需要后台允许权限-com.bestcxx.stu.springbootsecuritydb.security.service.CustomUserService中ROLE_ADMIN和ROLE_COMMON

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" 
	  xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
<meta content="text/html;charset=UTF-8"/>
<title sec:authentication="name"></title> 
<link rel="stylesheet" th:href="@{css/bootstrap.min.css}" />
<style type="text/css">
body {
  padding-top: 50px;
}
.starter-template {
  padding: 40px 15px;
  text-align: center;
}
</style>
</head>
<body>
	 <nav class="navbar navbar-inverse navbar-fixed-top">
      <div class="container">
        <div class="navbar-header">
          <a class="navbar-brand" href="#">Spring Security演示</a>
        </div>
        <div id="navbar" class="collapse navbar-collapse">
          <ul class="nav navbar-nav">
           <li><a th:href="@{/}"> 首页 </a></li>
           
          </ul>
        </div><!--/.nav-collapse -->
      </div>
    </nav>
    
    
     <div class="container">

      <div class="starter-template">
      	<h1 th:text="${msg.title}"></h1>
		<p class="bg-primary" th:text="${msg.content}"></p>
		
		需要权限显示:<div sec:authorize="hasRole('ROLE_ADMIN')"> <!-- 3 -->
		 	<p class="bg-info" th:text="${msg.etraInfo}"></p>
		</div>	
		
		
        <form th:action="@{/logout}" method="post">
            <input type="submit" class="btn btn-primary" value="注销-框架自带功能"/>
        </form>
        
        <a th:href="@{/common/test}">权限控制URL:具有ROLE_COMMON权限的才可以访问本 URL</a>
      </div>

    </div>
    
	
</body>
</html>

error.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" 
	  xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
<meta content="text/html;charset=UTF-8"/>
<title sec:authentication="name"></title>
</head>
<body>
	 出错了
</body>
</html>

common/test.html  

该页面具有 ROLE_COMMON 权限的才可以访问

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" 
	  xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
<meta content="text/html;charset=UTF-8"/>
<title sec:authentication="name"></title>
</head>
<body>
	 测试-需要有 ROLE_COMMON 权限的才可以访问哦
</body>
</html>

14、application.properties

无需太多更改,因为并未实际和数据库交互,所以仅需配置一下日志打印

logging.level.org.springframework.security= DEBUG


15、github 代码

https://github.com/Bestcxy/Spring-boot/tree/master/Spring%20Boot/springbootsecuritydb









评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值