创建springboot项目
- 创建父工程security-oauth2-demo2
- 创建子工程oauth2-service
- 引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
- 配置文件application.yml
server:
port: 8081
servlet:
context-path: /demo2
- 创建controller
@RestController
public class UserController {
@RequestMapping("/hello")
public String hello(){
return "Hello world!";
}
}
- 项目结构
- 启动访问
整合security
-
引入依赖
<!--数据库依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>5.1.47</scope>
</dependency>
<!--security依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
- 创建数据库表
CREATE TABLE `security2`.`t_user` (
`id` int(0) NOT NULL AUTO_INCREMENT COMMENT '主键',
`username` varchar(100) COMMENT '账号',
`password` varchar(255) COMMENT '密码',
`realname` varchar(100) CHARACTER SET utf8 COMMENT '名称',
`auth` varchar(255) COMMENT '权限',
PRIMARY KEY (`id`)
);
INSERT INTO `t_user` VALUES (1, 'zhangsan', '123', '张三', 'p1');
INSERT INTO `t_user` VALUES (2, 'lisi', '202CB962AC59075B964B07152D234B70', '李四', 'p2');
- application.yml添加数据库配置
server:
port: 8081
servlet:
context-path: /demo2
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
validation-query: SELECT 1
username: root
password: root
url: jdbc:mysql://localhost:3306/security2?useUnicode=true&characterEncoding=utf-8
- 创建dao查询数据库
package com.security.oauth2.oauth2service.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import java.util.Map;
@Repository
public class UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 根据账号查询
* @return
*/
public Map<String,Object> getUserByUsername(String userName){
String sql = "select * from t_user where username=?";
Map<String, Object> user = jdbcTemplate.queryForMap(sql, new Object[]{userName});
return user;
}
}
- 创建MyUserDetailsService实现UserDetailsService中的登录验证方法
package com.security.oauth2.oauth2service.config.security;
import com.security.oauth2.oauth2service.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
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.Component;
import java.util.Map;
/**
* 重写登录规则,并加入容器
*/
@Component
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UserDao userDao;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("username:"+username);
//从数据库查询
Map<String,Object> dbUser = userDao.getUserByUsername(username);
if (dbUser == null){
return null;
}
//根据用户名查询用户权限
String[] premiss = dbUser.get("auth").toString().split(",");
UserDetails build = User
//账号
.withUsername(dbUser.get("username").toString())
//密码
.password(dbUser.get("password").toString())
//权限
.authorities(premiss).build();
return build;
}
}
- 创建WebSecurityConfig文件继承WebSecurityConfigurerAdapter重写核心配置方法,并配置密码验证规则为不加密
package com.security.oauth2.oauth2service.config.security;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration //标志为一个配置文件
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder(){
//密码规则为原始数据,不加密
return NoOpPasswordEncoder.getInstance();
}
/**
* 认证策略,核心配置,具体的权限控制规则配置
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
//设置r1路径的访问权限是 p1
.antMatchers("/r/r1").hasAuthority("p1")
//设置r2路径的访问权限是 p2
.antMatchers("/r/r2").hasAuthority("p2")
//除了 /r/**.其他请求可以访问
.anyRequest().permitAll()
.and()
//开启表单登录,如果检测导没有登录会跳转到登录页
.formLogin();
}
}
- controller添加访问路径
package com.security.oauth2.oauth2service.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
@RequestMapping("/hello")
public String hello(){
return "Hello world!";
}
@RequestMapping("/r/r1")
public String r1(){
return "访问资源1!";
}
@RequestMapping("/r/r2")
public String r2(){
return "访问资源2!";
}
}
- 目录结构
- 启动访问http://localhost:8081/demo2/r/r1会跳转到登录
输入账号zhangsan密码123
点击登录
访问r2
启用权限注解
- 修改WebSecurityConfig配置文件
1、添加注解@EnableGlobalMethodSecurity(prePostEnabled = true),意思是开启前置权限注解@PrePostEnabled的使用
2、取消r1和r2的核心配置
package com.security.oauth2.oauth2service.config.security;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration //标志为一个配置文件
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder(){
//密码规则为原始数据,不加密
return NoOpPasswordEncoder.getInstance();
}
/**
* 认证策略,核心配置,具体的权限控制规则配置
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
//设置r1路径的访问权限是 p1
// .antMatchers("/r/r1").hasAuthority("p1")
// //设置r2路径的访问权限是 p2
// .antMatchers("/r/r2").hasAuthority("p2")
//请求可以访问
.anyRequest().permitAll()
.and()
//开启表单登录,如果检测导没有登录会跳转到登录页
.formLogin();
}
}
- 修改controller方法,添加@PrePostEnabled注解
package com.security.oauth2.oauth2service.controller;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
@RequestMapping("/hello")
public String hello(){
return "Hello world!";
}
@RequestMapping("/r/r1")
@PreAuthorize("hasAuthority('p1')")
public String r1(){
return "访问资源1!";
}
@RequestMapping("/r/r2")
@PreAuthorize("hasAuthority('p2')")
public String r2(){
return "访问资源2!";
}
}
- 访问r1
输入账号zhangsan密码123
访问r2
自定义密码加密规则
- 引入md5加密util
package com.security.oauth2.oauth2service.util;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 1.MD5加密字符串(32位大写)
* 2.MD5加密字符串(32位小写)
* <p>
* MD5在线加密:https://md5jiami.51240.com/
* 3.将二进制字节数组转换为十六进制字符串
* 4.Unicode中文编码转换成字符串
*/
public class MD5Util {
/**
* MD5加密字符串(32位大写)
*
* @param string 需要进行MD5加密的字符串
* @return 加密后的字符串(大写)
*/
public static String md5Encrypt32Upper(String string) {
byte[] hash;
try {
//创建一个MD5算法对象,并获得MD5字节数组,16*8=128位
hash = MessageDigest.getInstance("MD5").digest(string.getBytes("UTF-8"));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Huh, MD5 should be supported?", e);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("Huh, UTF-8 should be supported?", e);
}
//转换为十六进制字符串
StringBuilder hex = new StringBuilder(hash.length * 2);
for (byte b : hash) {
if ((b & 0xFF) < 0x10){
hex.append("0");
}
hex.append(Integer.toHexString(b & 0xFF));
}
return hex.toString().toUpperCase();
}
// public static void main(String[] args) {
// System.out.println(encodeMD5("123"));
// }
/**
* MD5加密字符串(32位小写)
*
* @param string 需要进行MD5加密的字符串
* @return 加密后的字符串(小写)
*/
public static String md5Encrypt32Lower(String string) {
byte[] hash;
try {
//创建一个MD5算法对象,并获得MD5字节数组,16*8=128位
hash = MessageDigest.getInstance("MD5").digest(string.getBytes("UTF-8"));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Huh, MD5 should be supported?", e);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("Huh, UTF-8 should be supported?", e);
}
//转换为十六进制字符串
StringBuilder hex = new StringBuilder(hash.length * 2);
for (byte b : hash) {
if ((b & 0xFF) < 0x10){
hex.append("0");
}
hex.append(Integer.toHexString(b & 0xFF));
}
return hex.toString().toLowerCase();
}
/**
* 将二进制字节数组转换为十六进制字符串
*
* @param bytes 二进制字节数组
* @return 十六进制字符串
*/
public static String bytesToHex(byte[] bytes) {
StringBuffer hexStr = new StringBuffer();
int num;
for (int i = 0; i < bytes.length; i++) {
num = bytes[i];
if (num < 0) {
num += 256;
}
if (num < 16) {
hexStr.append("0");
}
hexStr.append(Integer.toHexString(num));
}
return hexStr.toString().toUpperCase();
}
/**
* Unicode中文编码转换成字符串
*/
public static String unicodeToString(String str) {
Pattern pattern = Pattern.compile("(\\\\u(\\p{XDigit}{4}))");
Matcher matcher = pattern.matcher(str);
char ch;
while (matcher.find()) {
ch = (char) Integer.parseInt(matcher.group(2), 16);
str = str.replace(matcher.group(1), ch + "");
}
return str;
}
public static void main(String[] args) {
// System.out.println(md5Encrypt32Lower("oem"));
// System.out.println(md5Encrypt32Lower("djdqltj"));//大江东去浪淘尽
// System.out.println(md5Encrypt32Lower("SOC_SAFE_1149"));
// System.out.println(md5Encrypt32Lower("mhxzkhl"));//梅花香自苦寒来
// String password = DesUtil.decrypt("670B14728AD9902AECBA32E22FA4F6BD");
// System.out.println("password:"+password);
System.out.println(md5Encrypt32Upper("123"));
}
}
- 创建MD5PasswordEncoder实现PasswordEncoder的密码验证方法
package com.security.oauth2.oauth2service.config.security;
import com.security.oauth2.oauth2service.util.MD5Util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.crypto.password.PasswordEncoder;
public class MD5PasswordEncoder implements PasswordEncoder {
private Logger logger = LoggerFactory.getLogger(MD5PasswordEncoder.class);
@Override
public String encode(CharSequence rawPassword) {
if (rawPassword == null) {
throw new IllegalArgumentException("rawPassword cannot be null");
} else {
return MD5Util.md5Encrypt32Upper(rawPassword.toString());
}
}
/**
* 重写密码验证规则
*/
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
if (rawPassword == null) {
throw new IllegalArgumentException("rawPassword cannot be null");
} else if (encodedPassword != null && encodedPassword.length() != 0) {
return encode(rawPassword).equals(encodedPassword);
} else {
this.logger.warn("Empty encoded password");
return false;
}
}
@Override
public boolean upgradeEncoding(String encodedPassword) {
return false;
}
}
- 修改WebSecurityConfig中的密码bean
- 启动访问
登录zhangsan 123
登录lisi 123(数据库中是加密的md5串)
自定义登录页面
- 在resources添加static包,在static包下创建login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="login" method="post">
<input type="text" name="username" id="username"><br>
<input type="text" name="password" id="password"><br>
<input type="submit" value="提交">
</form>
</body>
</html>
- 修改WebSecurityConfig中的核心配置方法
package com.security.oauth2.oauth2service.config.security;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration //标志为一个配置文件
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder(){
// //密码规则为原始数据,不加密
// return NoOpPasswordEncoder.getInstance();
//密码规则为自定义md5规则
return new MD5PasswordEncoder();
}
/**
* 认证策略,核心配置,具体的权限控制规则配置
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//解决跨域问题
.csrf().disable()
.authorizeRequests()
//设置r1路径的访问权限是 p1
// .antMatchers("/r/r1").hasAuthority("p1")
// //设置r2路径的访问权限是 p2
// .antMatchers("/r/r2").hasAuthority("p2")
//请求可以访问
.anyRequest().permitAll()
.and()
//开启表单登录,如果检测导没有登录会跳转到登录页
.formLogin()
//登录页面
.loginPage("/login.html")
//定义登录方法
.loginProcessingUrl("/login")
//自定义登录成功的页面地址
.successForwardUrl("/login-success")
.and()
.sessionManagement()
//登陆时,如果需要就创建一个session
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);;
}
}
- 添加controller方法
@RequestMapping(value = "/login-success",produces = "text/plain;charset=UTF-8")
public String loginSuccess(HttpServletRequest req){
System.out.println("登录成功");
return "登录成功";
}
- 启动访问 http://localhost:8081/demo2/r/r2,看会不会自动跳转到自定义页面
登录 lisi 123