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/780287933、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.javapackage 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相关文章推荐
- springBoot+springSecurity验证密码MD5加密
- spring-security学习笔记--配置文件
- springBoot+springSecurity 数据库动态管理用户、角色、权限(二)
- 【Spring实战】----Security4.1.3实现根据请求跳转不同登录页以及登录后根据权限跳转到不同页配置
- SpringBoot+Shiro学习之密码加密和登录失败次数限制
- springBoot+springSecurity 数据库动态管理用户、角色、权限(二)
- Spring-Security (学习记录五)--配置登录时,密码采用md5加密,以及获取登录信息属性监听同步自己想要的登录信息
- springBoot+springSecurity验证密码MD5加密
- spirng-boot中,基于既有的token验证方式,利用spring-security实现权限系统
- SpringBoot+Shiro学习之密码加密和登录失败次数限制
- Spring boot 和 mybatis 学习笔记3--动态sql
- spring-security学习笔记--配置文件
- SpringBoot+Shiro学习之密码加密和登录失败次数限制示例
- SpringBoot学习笔记(7) SpringBoot整合Dubbo(使用yml配置)
- springBoot+springSecurity 动态管理Restful风格权限(三)
- SpringBoot学习笔记(四) SpringBoot Web相关的自动配置
- spring-security学习笔记--配置文件
- springboot 学习笔记(二)--- properties 配置
- springboot学习笔记-2 一些常用的配置以及整合mybatis
- spring-security学习笔记--配置文件