从零开始搭建springboot-security

阅读 49

2022-04-24

创建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`.`Untitled`  (
  `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串)

 

 

 

  •  

 

精彩评论(0)

0 0 举报