Spring-Boot-Security实现权限管理+MD5加密:
1.在pom.xml中引入SpringBoot集成SpringSecurity的依赖包
<!-- Spring-security -->
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
2.添加实体类-权限对象Admin、Employee:
此处去除了get、set方法,可以idea自动生成,也可以引入lombok依赖包,用@Data注解生成
package com.mybatis.code.demo.entity;
public class Admin {
private String username;
private String password;
}
package com.mybatis.code.demo.entity;
public class Employee {
private String id;
private String username;
private String password;
}
3.添加SecurityConfiguration配置类,继承WebSecurityConfigurerAdapter配置角色权限:
package com.mybatis.code.demo.config;
import org.springframework.beans.factory.annotation.Autowired;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Bean
public PasswordEncoder passwordEncoder() {
return new OverPasswordEncoder();
}
@Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
// 这里的passwordEncoder()调用了上面的Bean,返回的是new OverPasswordEncoder()
// passwoldEncoder是对密码的加密处理,如果user中密码没有加密,则可以不加此方法。注意加密请使用security自带的加密方式。
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()//禁用了 csrf 功能
.authorizeRequests()//限定签名成功的请求
//对decision和govern和employee 下的接口 需要 EMPLOYEE 或者 ADMIN 权限
.antMatchers("/decision/**","/govern/**","/employee/**").hasAnyRole("EMPLOYEE","ADMIN")
//对admin下的接口 需要ADMIN权限(只对admin开放)
.antMatchers("/admin/**").hasRole("ADMIN")
//不拦截 oauth 开放的资源(对任何权限开放,甚至没有权限)
.antMatchers("/oauth/**").permitAll()
//其他没有限定的请求,允许访问
.anyRequest().permitAll()
.and().anonymous()//对于没有配置权限的其他请求允许匿名访问
.and().formLogin()//使用 spring security 默认登录页面
.and().httpBasic();//启用http 基础验证
}
}
4.重写PasswordEncoder接口的encode、matches方法,实现MD5加密、验证:
OverPasswordEncoder.java:
package com.mybatis.code.demo.config;
import com.mybatis.code.demo.utils.MD5UtilBetter;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
@Component
public class OverPasswordEncoder implements PasswordEncoder {
@Override
public String encode(CharSequence rawPassword) {
String salt = MD5UtilBetter.getSalt();
return MD5UtilBetter.generate((String)rawPassword,salt);
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
System.out.println("用户输入的密码:"+rawPassword);
System.out.println("数据库中加密后的的密码:"+encodedPassword);
System.out.println("用户输入密码和数据库中的密码是否相等:"+MD5UtilBetter.verify((String)rawPassword,encodedPassword));
System.out.println("========================================");
return MD5UtilBetter.verify((String)rawPassword,encodedPassword);
}
}
MD5UtilBetter.java:
package com.mybatis.code.demo.utils;
import org.apache.commons.codec.binary.Hex;
import java.security.MessageDigest;
import java.util.Random;
/**
* 一般使用的加盐:
* md5(Password+UserName),即将用户名和密码字符串相加再MD5,这样的MD5摘要基本上不可反查。
* 但有时候用户名可能会发生变化,发生变化后密码即不可用了(验证密码实际上就是再次计算摘要的过程)。
* ----------
* 因此我们做了一个非常简单的加盐算法,每次保存密码到数据库时,都生成一个随机16位数字,将这16位数字和密码相加再求MD5摘要,然后在摘要中再将这16位数字按规则掺入形成一个48位的字符串。
* 在验证密码时再从48位字符串中按规则提取16位数字,和用户输入的密码相加再MD5。按照这种方法形成的结果肯定是不可直接反查的,且同一个密码每次保存时形成的摘要也都是不同的。
* @Author: Csa
*/
public class MD5UtilBetter {
private static Integer MAX = 16; //32位随机数
/**
* 获取随机盐
* 盐被称作“Salt值”,这个值是由系统随机生成的,并且只有系统知道。即便两个用户使用了同一个密码,由于系统为它们生成的salt值不同,散列值也是不同的。
*/
public static String getSalt(){
StringBuffer buffer = new StringBuffer("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
StringBuffer sb = new StringBuffer();
Random random = new Random();
int range = buffer.length();
for (int i = 0; i < MAX; i ++) {
sb.append(buffer.charAt(random.nextInt(range)));
}
return sb.toString();
}
/**
* 获取十六进制字符串形式的MD5摘要
*/
public static String md5Hex(String src) {
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
byte[] bs = md5.digest(src.getBytes());
return new String(new Hex().encode(bs));
} catch (Exception e) {
return null;
}
}
/**
* 生成含有随机盐的密码
*/
public static String generate(String password, String salt) {
password = md5Hex(password + salt);
char[] cs = new char[48];
for (int i = 0; i < 48; i += 3) {
cs[i] = password.charAt(i / 3 * 2);
char c = salt.charAt(i / 3);
cs[i + 1] = c;
cs[i + 2] = password.charAt(i / 3 * 2 + 1);
}
return new String(cs);
}
/**
* 校验密码是否正确
*/
public static boolean verify(String password, String md5) {
char[] cs1 = new char[32];
char[] cs2 = new char[16];
for (int i = 0; i < 48; i += 3) {
cs1[i / 3 * 2] = md5.charAt(i);
cs1[i / 3 * 2 + 1] = md5.charAt(i + 2);
cs2[i / 3] = md5.charAt(i + 1);
}
// 获取md5加盐加密后密码中的盐
String salt = new String(cs2);
System.out.println("校验----原来密码获得的md5加密加盐字符串:"+md5Hex(password + salt));
System.out.println("校验----输入密码获得的md5加密加盐字符串:"+new String(cs1));
return md5Hex(password + salt).equals(new String(cs1));
}
public static void main(String[] args) {
String salt = getSalt();
System.out.println("输出盐:"+salt);
String password = generate("123456", salt);
System.out.println("输出加盐--并用相应规则转换后的密码:"+password);
System.out.println("输出密码长度:"+password.length());
System.out.println("输出输入密码是否正确:"+verify("123456", password));
}
}
5.创建用户UserDetailServiceImpl实现UserDetailsService接口:
此处为了方便,不连接数据库,使用假数据进行模拟,生成环境使用数据替代即可。
package com.mybatis.code.demo.service.impl;
import com.mybatis.code.demo.config.OverPasswordEncoder;
import com.mybatis.code.demo.entity.Admin;
import com.mybatis.code.demo.entity.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
private OverPasswordEncoder overPasswordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
System.out.println("权限获取到的用户名:"+username);
//生成环境是查询数据库获取username的角色用于后续权限判断(如:张三 admin)
//这里不做数据库操作,给定假数据,有兴趣的人可以使内存模式。
if (username.equals("employee")) {
Employee employee = new Employee();
employee.setUsername("employee");
employee.setPassword("111111");
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_EMPLOYEE");
grantedAuthorities.add(grantedAuthority);
//创建一个用户,用于判断权限,请注意此用户名和方法参数中的username一致;BCryptPasswordEncoder是用来演示加密使用。
return new User(employee.getUsername(), overPasswordEncoder.encode(employee.getPassword()), grantedAuthorities);
}
if (username.equals("admin")) {
Admin admin = new Admin();
admin.setUsername("admin");
admin.setPassword("123456");
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_ADMIN");
grantedAuthorities.add(grantedAuthority);
return new User(admin.getUsername(), overPasswordEncoder.encode(admin.getPassword()), grantedAuthorities);
}
else {
return null;
}
}
}
6.编写Security权限测试Controller类:
package com.mybatis.code.demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class SecurityController {
/**
* 其他没有限定的内容,可以访问
* @return String
*/
@GetMapping("/helloWorld")
@ResponseBody
public String helloWorld(){
return "其他没有限定的内容,可以访问!";
}
/**
* 不拦截 oauth 开放的资源
* @return String
*/
@GetMapping("/oauth/login")
@ResponseBody
public String oauthLogin() {
return "不拦截 oauth 开放的资源!";
}
/**
* 对admin下的接口 需要ADMIN权限(只对admin开放)
* @return String
*/
@GetMapping("/admin/greeting")
@ResponseBody
public String adminGreeting() {
return "你的权限为Admin,恭喜访问成功!";
}
/**
* 对decision和govern和employee 下的接口 需要 EMPLOYEE 或者 ADMIN 权限
* @return String
*/
@GetMapping("/employee/greeting")
@ResponseBody
public String employeeGreeting() {
return "此为员工页面,你的权限为Employee 或者 Admin,恭喜你访问成功!";
}
/**
* 对decision和govern和employee 下的接口 需要 EMPLOYEE 或者 ADMIN 权限
* @return String
*/
@GetMapping("/decision/greeting")
@ResponseBody
public String decisionGreeting() {
return "此为运营页面,你的权限为Employee 或者 Admin,恭喜你访问成功!";
}
/**
* 对decision和govern和employee 下的接口 需要 EMPLOYEE 或者 ADMIN 权限
* @return String
*/
@GetMapping("/govern/greeting")
@ResponseBody
public String governGreeting() {
return "此为治理页面,你的权限为Employee 或者 Admin,恭喜你访问成功!";
}
}
7.使用Postman进行测试:
①.他没有限定的内容,可以访问
②.对decision和govern和employee 下的接口 需要 EMPLOYEE 或者 ADMIN 权限