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

1、Spring Security 基本介绍

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

比对Spring Boot 实现最简单的 Security

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

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

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

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



具体场景举例:

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

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

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

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

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



2、使用自动生成代码

http://blog.csdn.net/bestcxx/article/details/78028793


3、Maven 补全

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


			org.thymeleaf.extras
			thymeleaf-extras-springsecurity4
		

现在我依旧把pom.xml文件的完整内容粘贴出来



	4.0.0

	com.bestcxx.stu
	springbootsecuritydb
	0.0.1-SNAPSHOT
	war

	springbootsecuritydb
	Spring Boot 结合 Spring Security 

	
		org.springframework.boot
		spring-boot-starter-parent
		1.5.7.RELEASE
		 
	

	
		UTF-8
		UTF-8
		1.8
	

	
		
			org.springframework.boot
			spring-boot-starter-security
		
		
			org.springframework.boot
			spring-boot-starter-thymeleaf
		
		
		
			org.thymeleaf.extras
			thymeleaf-extras-springsecurity4
		
		
		
			org.springframework.boot
			spring-boot-starter-web
		

		
			org.springframework.boot
			spring-boot-starter-tomcat
			provided
		
		
			org.springframework.boot
			spring-boot-starter-test
			test
		
		
			org.springframework.security
			spring-security-test
			test
		
		
		
	

	
		
			
				org.springframework.boot
				spring-boot-maven-plugin
			
		
	




4、代码结构

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


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 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 roleList=new ArrayList();
		
		List authorities=new ArrayList();
		
		//遍历 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 才可以访问的路径   去test.html
		.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 msg=new HashMap();
			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页面

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

login.html





登录页面




	
	 
     

已成功注销

有错误,请重试

使用账号密码登录


home.html   

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





 




	 
    
    
     
    
	



error.html








	 出错了



common/test.html  

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








	 测试-需要有 ROLE_COMMON 权限的才可以访问哦



14、application.properties

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

logging.level.org.springframework.security= DEBUG


15、github 代码

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









你可能感兴趣的:(#,Spring,boot,#,Spring,Security)