SpringSecurity+OAuth2权限管理实战

Spring Security快速入门

官方文档:

Spring Security :: Spring Security

功能:

  • 身份认证(authentication)

  • 授权(authorization)

  • 防御常见攻击(protection against common attacks)

身份认证:

  • 身份认证是验证谁正在访问系统资源,判断用户是否为合法用户。认证用户的常见方式是要求用户输入用户名和密码。

授权:

  • 用户进行身份认证后,系统会控制谁能访问哪些资源,这个过程叫做授权。用户无法访问没有权限的资源。

防御常见攻击:

  • CSRF

  • HTTP Headers

  • HTTP Requests

1、身份认证(authentication)

官方代码示例:GitHub - spring-projects/spring-security-samples

1.1、创建Spring Boot项目

项目名:security-demo

JDK:17

SpringBoot:3.2.0(依赖了Spring Security 6.2.0)

Dependencies:Spring Web、Spring Security、Thymeleaf

1.2、创建IndexController

package com.atguigu.securitydemo.controller;
​
@Controller
public class IndexController {
​
    @GetMapping("/")
    public String index() {
        return "index";
    }
}

1.3、创建index.html

在路径resources/templates中创建index.html

<html xmlns:th="https://www.thymeleaf.org">
<head>
  <title>Hello Security!</title>
</head>
<body>
<h1>Hello Security</h1>
<!--通过使用@{/logout},Thymeleaf将自动处理生成正确的URL,以适应当前的上下文路径。
这样,无论应用程序部署在哪个上下文路径下,生成的URL都能正确地指向注销功能。-->
<a th:href="@{/logout}">Log Out</a>
</body>
</html>

1.4、启动项目测试Controller

浏览器中访问:http://localhost:8080/

浏览器自动跳转到登录页面:http://localhost:8080/login

输入用户名:user

输入密码:在控制台的启动日志中查找初始的默认密码

点击"Sign in"进行登录,浏览器就跳转到了index页面

1.5、注意事项

1.5.1、@{/logout}的作用

通过使用@{/logout},Thymeleaf将自动处理生成正确的URL,以适应当前的上下文路径。这样,无论应用程序部署在哪个上下文路径下,生成的URL都能正确地指向注销功能。

例如:如果我们在配置文件中添加如下内容

server.servlet.context-path=/demo

那么@{/logout}可以自动处理url为正确的相对路径

但是如果是普通的/logout,路径就会不正确

1.5.2、页面样式无法加载的问题

页面样式bootstrap.min.css是一个CDN地址,需要通过科学上网的方式访问

否则你的登录页会加载很久,并且看到的页面是这样的(登录按钮没有样式文件渲染,但是不影响登录功能的执行)

1.6、Spring Security默认做了什么

  • 保护应用程序URL,要求对应用程序的任何交互进行身份验证。

  • 程序启动时生成一个默认用户“user”。

  • 生成一个默认的随机密码,并将此密码记录在控制台上。

  • 生成默认的登录表单和注销页面。

  • 提供基于表单的登录和注销流程。

  • 对于Web请求,重定向到登录页面;对于服务请求,返回401未经授权。

  • 处理跨站请求伪造(CSRF)攻击。

  • 处理会话劫持攻击。

  • 写入Strict-Transport-Security以确保HTTPS。

  • 写入X-Content-Type-Options以处理嗅探攻击。

  • 写入Cache Control头来保护经过身份验证的资源。

  • 写入X-Frame-Options以处理点击劫持攻击。

1.7创建项目

ide社区版本或者专业版2022-2023

服务器地址URL 阿里云脚手架 https://start.aliyun.com/

或者原来的即可

http://start.springboot.io/

Spring Web Spring Security Thymeleaf 三个选中

然后创建

IndexController
  // http://127.0.0.1:8080/
    @GetMapping("/")
    public String html(){
        return "index.html";
    }

templates/ index.html

<html xmlns:th="https://www.thymeleaf.org">
<head>
    <title>Hello Security!</title>
</head>
<body>
<h1>Hello Security</h1>
<a th:href="@{/logout}">Log Out</a>
<a href="@{/logout}">Log Out2</a>
</body>
</html>

SecurityProperties

默认情况下Spring Security将初始的用户名和密码存在了SecurityProperties类中。这个类中有一个静态内部类User,配置了默认的用户名(name = "user")和密码(password = uuid)

spring.security.user.name=user
spring.security.user.password=123

WebSecurityConfig

package com.smg.securitydemo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

@Configuration
@EnableWebSecurity//Spring项目总需要添加此注解,SpringBoot项目中不需要
public class WebSecurityConfig {

    @Bean
    public UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser( //此行设置断点可以查看创建的user对象
                User
                        .withDefaultPasswordEncoder()
                        .username("huan") //自定义用户名
                        .password("password") //自定义密码
                        .roles("USER") //自定义角色
                        .build()
        );
        return manager;
    }
}

 http://127.0.0.1:8080/

之前密码不可用

2、基于数据库的数据源

2.1、SQL

创建三个

- 创建数据库
CREATE DATABASE `security-demo`;
USE `security-demo`;

-- 创建用户表
CREATE TABLE `user`(
	`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
	`username` VARCHAR(50) DEFAULT NULL ,
	`password` VARCHAR(500) DEFAULT NULL,
	`enabled` BOOLEAN NOT NULL
);
-- 唯一索引
CREATE UNIQUE INDEX `user_username_uindex` ON `user`(`username`); 

-- 插入用户数据(密码是 "abc" )
INSERT INTO `user` (`username`, `password`, `enabled`) VALUES
('admin', '{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW', TRUE),
('Helen', '{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW', TRUE),
('Tom', '{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW', TRUE);

数据库表并插入测试数据

2.2、引入依赖

 <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.30</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.4.1</version>
            <exclusions>
                <exclusion>
                    <groupId>org.mybatis</groupId>
                    <artifactId>mybatis-spring</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>3.0.3</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.10</version>
        </dependency>

2.3、配置数据源

#MySQL数据源
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/security-demo
spring.datasource.username=root
spring.datasource.password=123456
#SQL日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

2.4、实体类

package com.smg.securitydemo.entity;

@Data
public class User {

    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    private String username;

    private String password;

    private Boolean enabled;

}

2.5、Mapper

package com.smg.securitydemo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.smg.securitydemo.entity.User;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper extends BaseMapper<User> {
}

xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.smg.securitydemo.mapper.UserMapper">

</mapper>

2.6、Service

package com.smg.securitydemo.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.smg.securitydemo.entity.User;

public interface UserService extends IService<User> {
}

实现

package com.smg.securitydemo.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.smg.securitydemo.entity.User;
import com.smg.securitydemo.mapper.UserMapper;
import com.smg.securitydemo.service.UserService;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}

2.7、Controller

package com.smg.securitydemo.controller;

import com.smg.securitydemo.entity.User;
import com.smg.securitydemo.service.UserService;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/user")
public class UserController {
    //localhost:8080/demo/user/list
    @Resource
    public UserService userService;

    @GetMapping("/list")
    public List<User> getList(){
        return userService.list();
    }
}

登录失败

原因springboot版本太新导致不兼容

修改过后即可访问

   <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.4.1</version>
            <exclusions>
                <exclusion>
                    <groupId>org.mybatis</groupId>
                    <artifactId>mybatis-spring</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>3.0.3</version>
        </dependency>

修改过后登录即可

3、基于数据库的用户认证

3.1、基于数据库的用户认证流程

3.2、定义DBUserDetailsManager

package com.smg.securitydemo.config;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.smg.securitydemo.entity.User;
import com.smg.securitydemo.mapper.UserMapper;
import jakarta.annotation.Resource;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsPasswordService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.provisioning.UserDetailsManager;

import java.util.ArrayList;
import java.util.Collection;

public class DBUserDetailsManager implements UserDetailsManager, UserDetailsPasswordService {

    @Resource
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("username", username);
        User user = userMapper.selectOne(queryWrapper);
        if (user == null) {
            throw new UsernameNotFoundException(username);
        } else {
            Collection<GrantedAuthority> authorities = new ArrayList<>();
            return new org.springframework.security.core.userdetails.User(
                    user.getUsername(),
                    user.getPassword(),
                    user.getEnabled(),
                    true, //用户账号是否过期
                    true, //用户凭证是否过期
                    true, //用户是否未被锁定
                    authorities); //权限列表
        }
    }

    @Override
    public UserDetails updatePassword(UserDetails user, String newPassword) {
        return null;
    }

    @Override
    public void createUser(UserDetails user) {

    }

    @Override
    public void updateUser(UserDetails user) {

    }

    @Override
    public void deleteUser(String username) {

    }

    @Override
    public void changePassword(String oldPassword, String newPassword) {

    }

    @Override
    public boolean userExists(String username) {
        return false;
    }

}

3.3、初始化UserDetailsService

修改WebSecurityConfig中的userDetailsService方法如下

  @Bean
    public UserDetailsService userDetailsService() {
        DBUserDetailsManager manager = new DBUserDetailsManager();
        return manager;
    }

注意需要注释掉原来的代码

遇到这种问题

在 

WebSecurityConfig中创建对应缺少的类
@Bean
public DBUserDetailsManager2 dbUserDetailsManager2() {
    // 创建DBUserDetailsManager2的实例,这里可能需要注入其他依赖,如JdbcTemplate等
    return new DBUserDetailsManager2();
}

登录数据库里的账号如admin

Swagger测试地址:http://localhost:8080/demo/doc.html

6、密码加密算法

参考文章

https://docs.spring.io/spring-security/reference/features/authentication/password-storage.html

6.1、密码加密方式

明文密码:

项目地址

security-demo: security-demo

oauth2-login-demo: oauth2-login-demo

  • 19
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SpringCloud是一款开源的微服务框架,OAuth2是一种授权框架,Vue是一个流行的前端框架,ElementUI是一套基于Vue开发的UI框架。结合这些技术栈进行前后端分离的快速上手项目实战开发,可以提高开发效率和代码的可维护性。 实践中,可以按照以下步骤进行快速上手项目开发: 1. 搭建后端服务:使用SpringCloud搭建微服务架构,并引入Spring SecurityOAuth2来实现认证和授权功能,确保后端接口的安全性。 2. 配置OAuth2服务端:在后端服务中配置OAuth2的服务端,定义认证服务器和资源服务器,配置客户端信息,如客户端ID、客户端密钥等。 3. 开发前端界面:使用Vue构建前端界面,并引入ElementUI来快速搭建页面和组件。利用Vue的组件化开发方式,可以更高效地开发各种交互功能。 4. 实现登录认证:在前端界面中使用OAuth2的授权码模式来实现用户登录认证功能,通过向认证服务器发送请求来获取访问令牌,并将令牌保存到前端的Cookie或localStorage中。 5. 发起请求并解析响应:在前端界面中使用Axios库来发起HTTP请求,并在请求头中携带访问令牌,后端服务器根据令牌进行权限验证。前端收到响应后解析数据,并进行相应的操作。 6. 实现权限控制:根据后端接口的权限设定,在前端界面中进行权限控制,隐藏或禁用没有权限的功能。可以通过在请求头中携带用户的角色信息,与后端进行验证。 7. 编写测试用例:保证代码的质量和功能的稳定性,编写相应的测试用例来进行单元测试和接口测试,确保项目的正确运行。 通过以上步骤,可以快速上手并实战开发SpringCloud、OAuth2、Vue和ElementUI结合的前后端分离项目。不仅可以提高开发效率,还能保证项目的安全性和可维护性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值