您的位置:首页 > 编程语言 > Java开发

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

2017-09-21 09:55 1116 查看

1、Spring Security 基本介绍

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

比对Spring Boot 实现最简单的 Security

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

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

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

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

具体场景举例:

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

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

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

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

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

2、使用自动生成代码

http://blog.csdn.net/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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Spring Boot Security