SpringSecurity实现自定义用户认证方案

Spring Security 实现自定义用户认证方案可以根据具体需求和业务场景进行设计和实施,满足不同的安全需求和业务需求。这种灵活性使得认证机制能够更好地适应各种复杂的环境和变化‌。通过自定义认证方案,可以更好地控制和管理用户的访问权限,确保数据和应用程序的安全性和可靠性。

基于 Spring Security 自定义用户认证方案的开发流程如下图:

UserDetails 接口代表用户详细信息,而负责对 UserDetails 进行各种操作的则是 UserDetailsService 接口。因此,实现自定义用户认证方案首先要做的是实现 UserDetails 和 UserDetailsService 接口。同时,如果扩展了用信息,可以结合 AuthenticationProvider 接口来扩展整个认证流程。

1、SpringBoot 整合 SpringSecurity 框架

【示例】SpringBoot 整合 SpringSecurity 创建一个自定义用户认证应用。

1.1 创建 Spring Boot 项目

创建 SpringBoot 项目,项目结构如下图:

1.2 添加 Maven 依赖

在 pom.xml 配置文件中添加 Spring Security、Thymeleaf 模板引擎、Lombok 依赖。

<!-- Spring Security 依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    <version>2.7.18</version>
</dependency>

<!-- Lombok 依赖 -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

<!-- 引入Thymeleaf模板引擎 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

2、Spring Security 实现自定义用户认证方案

Spring Security 所做的工作只是把常见的、符合一般业务场景的实现方法进行抽象并嵌入框架中,开发人员完全可以自定义用户认证方案。

2.1 扩展 UserDetails

扩展 UserDetails 的方法是直接实现该接口。

package com.pjb.model;

import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.List;

/**
 * 自定义用户认证类:扩展 UserDetails 接口
 * @author pan_junbiao
 **/
@Data
public class LoginUserDetails implements UserDetails
{
    private Long userId;
    private String username;
    private String password;
    private String BlogName; //博客名称
    private String BlogUrl;  //博客地址
    private List<GrantedAuthority> authoritys; //权限集合

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities()
    {
        return this.authoritys;
    }

    public void setAuthoritys(List<GrantedAuthority> authoritys)
    {
        this.authoritys = authoritys;
    }

    @Override
    public boolean isAccountNonExpired()
    {
        return true;
    }

    @Override
    public boolean isAccountNonLocked()
    {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired()
    {
        return true;
    }

    @Override
    public boolean isEnabled()
    {
        return true;
    }
}

2.2 扩展 UserDetailsService

接下来我们实现 UserDetailsService 接口。

package com.pjb.service;

import com.pjb.model.LoginUserDetails;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
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.List;

/**
 * 登录服务类:扩展 UserDetailsService 接口
 * @author pan_junbiao
 **/
@Service
public class LoginUserDetailsService implements UserDetailsService
{

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
    {
        //模拟数据库查询
        LoginUserDetails user = this.findByUsername(username);
        if (user != null)
        {
            return user;
        }

        //未查询到用户信息,则抛出异常
        throw new UsernameNotFoundException("未找到用户名称为:" + username + "的用户信息");
    }

    /**
     * 模拟数据库查询
     */
    private LoginUserDetails findByUsername(String username)
    {
        LoginUserDetails loginUserDetails = null;
        if (username != null && username.equals("panjunbiao"))
        {
            loginUserDetails = new LoginUserDetails();
            loginUserDetails.setUserId(1L);
            loginUserDetails.setUsername(username);
            loginUserDetails.setPassword("123456");
            loginUserDetails.setBlogName("您好,欢迎访问 pan_junbiao的博客");
            loginUserDetails.setBlogUrl("https://blog.csdn.net/pan_junbiao");

            //设置权限
            List<GrantedAuthority> grantedAuthorityList = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER");
            loginUserDetails.setAuthoritys(grantedAuthorityList);
        }
        return loginUserDetails;
    }
}

2.3 扩展 AuthenticationProvider

扩展 AuthenticationProvider 是实现自定义认证流程的最后一步,这个过程提供一个自定义的 AuthenticationProvider 实现类。

package com.pjb.provider;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

/**
 * 登录认证类:扩展 AuthenticationProvider
 * @author pan_junbiao
 **/
@Component
public class LoginAuthenticationProvider implements AuthenticationProvider
{
    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException
    {
        //从 Authentication 对象中获取用户名称和身份凭证信息
        String username = authentication.getName();
        String password = authentication.getCredentials().toString();

        UserDetails user = userDetailsService.loadUserByUsername(username);
        if (passwordEncoder.matches(password, user.getPassword()))
        {
            //密码匹配成功,则构建一个 UsernamePasswordAuthenticationToken 对象并返回
            return new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), user.getAuthorities());
        } else
        {
            //密码匹配失败,则抛出异常
            throw new BadCredentialsException("用户密码不正确");
        }
    }

    @Override
    public boolean supports(Class<?> authenticationType)
    {
        return authenticationType.equals(UsernamePasswordAuthenticationToken.class);
    }
}

3、Spring Security 的处理类

3.1 登录成功处理类

package com.pjb.handler;

import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 登录成功处理类
 */
@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler
{
    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException
    {
        //重定向至首页
        httpServletResponse.sendRedirect("/");
    }
}

3.2 登录失败处理类

package com.pjb.handler;

import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * 登录失败处理类
 */
@Component
public class LoginFailureHandler implements AuthenticationFailureHandler
{
    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException authenticationException) throws IOException, ServletException
    {
        //获取登录失败原因
        String errorMessage = "";
        if(authenticationException instanceof BadCredentialsException){
            errorMessage = "用户名或密码不正确";
        }else if(authenticationException instanceof DisabledException){
            errorMessage = "账号被禁用";
        }else if(authenticationException instanceof UsernameNotFoundException){
            errorMessage = "用户名不存在";
        }else if(authenticationException instanceof CredentialsExpiredException){
            errorMessage = "密码已过期";
        }else if(authenticationException instanceof LockedException) {
            errorMessage = "账号被锁定";
        }else{
            errorMessage = "未知异常";
        }

        //设置响应编码
        httpServletResponse.setContentType("application/json;charset=utf-8");
        PrintWriter out = httpServletResponse.getWriter();
        out.write(errorMessage);
    }
}

3.3 403无权限处理类

package com.pjb.handler;

import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * 403无权限处理类
 */
@Component
public class PermissionDeniedHandler implements AccessDeniedHandler
{
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException
    {
        httpServletResponse.setContentType("application/json;charset=utf-8");
        PrintWriter out = httpServletResponse.getWriter();
        out.write("403无权限");
    }
}

4、Spring Security 的核心配置类

创建 WebSecurityConfig 类(Spring Security 配置类),并添加 @EnableWebSecurity 注解和继承 WebSecurityConfigurerAdapter 类。

package com.pjb.config;

import com.pjb.handler.LoginFailureHandler;
import com.pjb.handler.LoginSuccessHandler;
import com.pjb.handler.PermissionDeniedHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationProvider;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * Spring Security 配置类
 * @author pan_junbiao
 **/
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
{
    @Autowired
    private LoginSuccessHandler loginSuccessHandler;

    @Autowired
    private LoginFailureHandler loginFailureHandler;

    @Autowired
    private PermissionDeniedHandler permissionDeniedHandler;

    //自定义的扩展 UserDetailsService 类
    @Autowired
    private UserDetailsService loginUserDetailsService;

    //自定义的扩展 AuthenticationProvider 类
    @Autowired
    private AuthenticationProvider loginAuthenticationProvider;

    @Override
    protected void configure(HttpSecurity http) throws Exception
    {
        http.authorizeRequests() //返回一个URL拦截注册器
                .anyRequest() //匹配所有的请求
                .authenticated() //所有匹配的URL都需要被认证才能访问
                .and() //结束当前标签,让上下文回到 HttpSecurity
                .formLogin() //启动表单认证
                .loginPage("/myLogin.html") //自定义登录页面
                .loginProcessingUrl("/auth/form") //指定处理登录请求路径
                .permitAll() //使登录页面不设限访问
                //.defaultSuccessUrl("/index") //登录认证成功后的跳转页面
                .successHandler(loginSuccessHandler) //指定登录成功时的处理
                .failureHandler(loginFailureHandler) //指定登录失败时的处理
                .and()
                .exceptionHandling().accessDeniedHandler(permissionDeniedHandler) //403无权时的返回操作
                .and().csrf().disable(); //关闭CSRF的防御功能
    }

    /**
     * 核心代码:
     * 将扩展的 LoginUserDetailsService 和 LoginAuthenticationProvider 注入,
     * 并将其添加到 AuthenticationManagerBuilder 中,这样 AuthenticationManagerBuilder 将基于上述
     * 自定义的 LoginUserDetailsService 来完成 UserDetails 的创建和管理,
     * 并基于自定义的 LoginAuthenticationProvider 完成用户认证。
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception
    {
        auth.userDetailsService(loginUserDetailsService);
        auth.authenticationProvider(loginAuthenticationProvider);
    }

    /**
     * 由于5.x版本之后默认启用了委派密码编译器,
     * 因而按照以往的方式设置内存密码将会读取异常,
     * 所以需要暂时将密码编码器设置为 NoOpPasswordEncoder
     */
    @Bean
    public PasswordEncoder passwordEncoder()
    {
        return NoOpPasswordEncoder.getInstance();
    }
}

将扩展的 LoginUserDetailsService 和 LoginAuthenticationProvider 注入,并将其添加到 AuthenticationManagerBuilder 中,这样 AuthenticationManagerBuilder 将基于上述自定义的 LoginUserDetailsService 来完成 UserDetails 的创建和管理,并基于自定义的 LoginAuthenticationProvider 完成用户认证。

5、前端页面

5.1 控制器层

创建 IndexController 类(首页控制器),实现获取当前登录用户名并跳转至首页。

package com.pjb.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;
import java.security.Principal;

/**
 * 首页控制器
 * @author pan_junbiao
 **/
@Controller
public class IndexController
{
    /**
     * 首页
     */
    @RequestMapping("/")
    public String index(HttpServletRequest request)
    {
        //获取当前登录人
        String userName = "未登录";
        Principal principal = request.getUserPrincipal();
        if (principal != null)
        {
            userName = principal.getName();
        }

        //返回页面
        request.setAttribute("userName", userName);
        return "/index.html";
    }
}

5.2 编写登录页面

在 resources\static 静态资源目录下,创建 myLogin.html 页面。

注意:myLogin.html 页面必须放在 resources\static 静态资源目录下,否则页面无法加载。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
    <meta name="author" content="pan_junbiao的博客">
</head>
<body>
<form name="myForm" action="/auth/form" method="post">
    <table align="center">
        <caption>用户登录</caption>
        <tr>
            <td>登录账户:</td>
            <td>
                <input type="text" name="username" placeholder="请输入登录账户" value="panjunbiao" />
            </td>
        </tr>
        <tr>
            <td>登录密码:</td>
            <td>
                <input type="password" name="password" placeholder="请输入登录密码" value="123456" />
            </td>
        </tr>
        <!-- 以下是提交、取消按钮 -->
        <tr>
            <td colspan="2" style="text-align: center; padding: 5px;">
                <input type="submit" value="提交" />
                <input type="reset" value="重置" />
            </td>
        </tr>
    </table>
</form>
</body>
</html>

5.3 编写首页

在 resources\templates 资源目录下,创建 index.html 页面。

注意:首页 index.html 页面中使用 Thymeleaf 模板 。

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
    <meta name="author" content="pan_junbiao的博客">
</head>
<body>
    <h1 style="color: red">Hello,Spring Security</h1>
    <p>博客信息:您好,欢迎访问 pan_junbiao的博客</p>
    <p>博客地址:https://blog.csdn.net/pan_junbiao</p>
    <p th:text="'当前登录人:' + ${userName}"></p>
    <a href="/logout" onclick="return confirm('确认注销吗?');">登出</a>
</body>
</html>

6、运行项目

6.1 登录页面

6.2 登录成功后,跳转至首页

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/958819.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

深入内核讲明白Android Binder【三】

深入内核讲明白Android Binder【三】 前言一、服务的获取过程内核源码解析1. 客户端获取服务的用户态源码回顾2. 客户端获取服务的内核源码分析2.1 客户端向service_manager发送数据1. binder_ioctl2. binder_ioctl_write_read3. binder_thread_write4. binder_transaction4.1 …

深入解析:Docker 容器如何实现文件系统与资源的多维隔离?

目录 一、RootFs1. Docker 镜像与文件系统层2. RootFs 与容器隔离的意义 二、Linux Namespace1. 进程命名空间1.1 lsns 命令说明1.2 查看“祖先进程”命名空间1.3 查看当前用户进程命名空间 2. 容器进程命名空间2.1 查看容器进程命名空间列表2.2 容器进程命名空间的具体体现 三…

解锁Java中的国密算法:安全保障的密钥

一、引言 在数字化浪潮席卷全球的当下&#xff0c;信息安全已然成为国家、企业乃至个人无法忽视的重要议题。国密算法&#xff0c;作为我国自主研发的密码算法体系&#xff0c;宛如坚固的盾牌&#xff0c;为国家信息安全筑起了一道坚不可摧的防线。它的诞生&#xff0c;不仅承载…

proxysql读写分离的部署

关闭防火墙和selinux systemctl stop firewalld systemctl disable firewalld setenforce 0设置主机名称 hostnamectl set-hostname zhangyijia-host71.database.com && bash hostnamectl set-hostname zhangyijia-host72.database.com && bash两台主机安装m…

Android各个版本存储权限适配

一、Android6.0-9.0 1、动态权限申请&#xff1a; private static String[] arrPermissions {android.Manifest.permission.READ_EXTERNAL_STORAGE, android.Manifest.permission.WRITE_EXTERNAL_STORAGE,android.Manifest.permission.ACCESS_FINE_LOCATION,android.Manifest.…

【xcode 16.2】升级xcode后mac端flutter版的sentry报错

sentry_flutter 7.11.0 报错 3 errors in SentryCrashMonitor_CPPException with the errors No type named terminate_handler in namespace std (line 60) and No member named set_terminate in namespace std 替换sentry_flutter版本为&#xff1a; 8.3.0 从而保证oc的…

ASP.NET Core 6.0 如何处理丢失的 Startup.cs 文件

介绍 .NET 6.0 已经发布&#xff0c;ASP.NET Core 6.0 也已发布。其中有不少变化让很多人感到困惑。例如&#xff0c;“谁动了我的奶酪”&#xff0c;它在哪里Startup.cs&#xff1f;在这篇文章中&#xff0c;我将深入研究这个问题&#xff0c;看看它移动到了哪里以及其他变化。…

idea plugin插件开发——入门级教程(IntelliJ IDEA Plugin)

手打不易&#xff0c;如果转摘&#xff0c;请注明出处&#xff01; 注明原文&#xff1a;idea plugin插件开发——入门级教程&#xff08;IntelliJ IDEA Plugin&#xff09;-CSDN博客 目录 前言 官方 官方文档 代码示例 开发前必读 Intellij、Gradle、JDK 版本关系 plu…

概率密度函数(PDF)分布函数(CDF)——直方图累积直方图——直方图规定化的数学基础

对于连续型随机变量&#xff0c;分布函数&#xff08;Cumulative Distribution Function, CDF&#xff09;是概率密度函数&#xff08;Probability Density Function, PDF&#xff09;的变上限积分&#xff0c;概率密度函数是分布函数的导函数。 如果我们有一个连续型随机变量…

三分钟简单了解一些HTML的标签和语法_02

1.a标签演示 点击然后跳转 代码加入title 2.图片链接 3.锚点链接 点击就会跳转的当前位置 4.a标签小知识补充 该实例会跳转到顶,锚点链接则会跳转到相应的锚点 5. 结果:直接跳转到该页面的锚点处 6. 在 HTML 中&#xff0c;<tr>标签表示表格中的行&#xff08;TableRow&…

mysql 学习3 SQL语句--整体概述。SQL通用语法;DDL创建数据库,查看数据库,删除数据库,使用数据库;

SQL通用语法 SQL语句分类 DDL data definition language : 用来创建数据库&#xff0c;创建表&#xff0c;创建表中的字段&#xff0c;创建索引。因此成为 数据定义语言 DML data manipulation language 有了数据库和表以及字段后&#xff0c;那么我们就需要给这个表中 添加数…

基于ollama,langchain,springboot从零搭建知识库三【解析文档并存储到向量数据库】

安装环境 安装pgvector&#xff0c;先设置docker镜像源&#xff1a; vim /etc/docker/daemon.json {"registry-mirrors": ["https://05f073ad3c0010ea0f4bc00b7105ec20.mirror.swr.myhuaweicloud.com","https://mirror.ccs.tencentyun.com",&…

C# OpenCV机器视觉:红外体温检测

在一个骄阳似火的夏日&#xff0c;全球却被一场突如其来的疫情阴霾笼罩。阿强所在的小镇&#xff0c;平日里熙熙攘攘的街道变得冷冷清清&#xff0c;人们戴着口罩&#xff0c;行色匆匆&#xff0c;眼神中满是对病毒的恐惧。阿强作为镇上小有名气的科技达人&#xff0c;看着这一…

计算机视觉算法实战——无人机检测

✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连✨ ​ ​ 1. 引言✨✨ 随着无人机技术的快速发展&#xff0c;无人机在农业、物流、监控等领域的应用越来越广泛。然而&#xff0c;无人机的滥用也带…

日志收集Day004

1.filebeat安装 基于二进制安装filebeat (1)下载filebeat软件包 (2)解压软件包 tar xf filebeat-7.17.5-linux-x86_64.tar.gz -C /app/softwares/ (3)验证filebeat安装是否成功 cd /app/softwares/filebeat-7.17.5-linux-x86_64/ ln -svf pwd/filebeat /usr/local/sbin/ f…

Vue入门(Vue基本语法、axios、组件、事件分发)

Vue入门 Vue概述 Vue (读音/vju/&#xff0c;类似于view)是一套用于构建用户界面的渐进式框架&#xff0c;发布于2014年2月。与其它大型框架不同的是&#xff0c;Vue被设计为可以自底向上逐层应用。Vue的核心库只关注视图层&#xff0c;不仅易于上手&#xff0c;还便于与第三…

缓存商品、购物车(day07)

缓存菜品 问题说明 问题说明&#xff1a;用户端小程序展示的菜品数据都是通过查询数据库获得&#xff0c;如果用户端访问量比较大&#xff0c;数据库访问压力随之增大。 结果&#xff1a; 系统响应慢、用户体验差 实现思路 通过Redis来缓存菜品数据&#xff0c;减少数据库查询…

数据结构测试题2

一、单选题&#xff08;每题 2 分&#xff0c;共20分&#xff09; 1. 栈和队列的共同特点是( A )。 A.只允许在端点处插入和删除元素 B.都是先进后出 C.都是先进先出 D.没有共同点 2. 用链接方式存储的队列&#xff0c;在进行插入运算时( C ) A. 仅修改头指针 B. 头…

qml Settings详解

1、概述 QML中的Settings类提供了一种便捷的方式来保存和恢复应用程序的配置信息&#xff0c;如用户名、密码、窗口位置和大小等。它简化了配置数据的存储过程&#xff0c;无需使用像SQLite这样的数据库系统。通过使用Settings&#xff0c;开发者可以轻松实现应用程序设置的持…

认识Django项目模版文件——Django学习日志(二)

1.默认文件介绍 └── djangoproject1/├── djangoproject1/│ ├── urls.py [URL和函数的对应关系]【常用文件】│ ├── settings.py [项目配置文件]【常用文件】│ ├── _init_.py│ ├── wsgi.py [接受网络请求] 【不要动】│ └──…