• JSD-2204-关于Spring Security和BCrypt-Day11


    1.Spring Security

    1.1关于Spring Security

    Spring Security是主要解决认证(Authenticate)和授权(Authorization)的框架。

    1.2添加依赖

    在Spring Boot项目中,添加spring-boot-starter-security依赖项。

    注意:以上依赖项是带有自动配置的,一旦添加此依赖,整个项目中所有的访问,默认都是必须先登录才可以访问的,在浏览器输入任何此服务的URL,都会自动跳转到默认的登录页面。

    默认的用户名是user,默认的密码是启动项目时自动生成的随机密码,在服务器端的控制台可以看到此密码。

    当登录后,会自动跳转到此前尝试访问的页面。

    Spring Security默认使用Session机制保存用户的登录状态,所以,重启服务后,登录状态会消失。在不重启的情况下,可以通过 /logout 访问“退出登录”页面,确定后也可以清除登录状态。

    1.3关于BCrypt

    在Spring Security中,内置了BCrypt算法的工具类,此工具类可以实现使用BCrypt算法对密码进行加密、验证密码的功能。

    BCrypt算法使用了随机盐,所以,多次使用相同的原文进行加密,得到的密文都将是不同的,并且,使用的盐值会作为密文的一部分,也就不会影响验证密码了。

    在Spring Security框架中,定义了PasswordEncoder接口,表示“密码编码器”,并且使用BCryptPasswordEncoder实现了此接口。

    1.4在添加管理员时,对密码进行加密

    通常,应该自定义配置类,在配置类中使用@Bean方法,使得Spring框架能创建并管理PasswordEncoder类型的对象,在后续使用过程中,可以自动装配此对象。

    在根包下创建config.SecurityConfiguration类:

    1. @Configuration
    2. public class SecurityConfiguration {
    3. @Bean
    4. public PasswordEncoder passwordEncoder() {
    5. return new BCryptPasswordEncoder();
    6. }
    7. }

    然后,在需要使用此对象的类中,自动装配即可,例如,在AdminServiceImpl类中添加:

    1. @Autowired
    2. private PasswordEncoder passwordEncoder;

    在此类中,就可以使用到以上属性,例如:

    1. String rawPassword = admin.getPassword();
    2. String encodedPassword = passwordEncoder.encode(rawPassword);
    3. admin.setPassword(encodedPassword);

    注意:一旦在Spring容器中已经存在PasswordEncoder对象,Spring Security会自动使用它,所以,会导致默认的随机密码不可用(你提交的随机密码会被加密后再进行对比,而Spring Security默认的密码并不是密文,所以对比会失败)。

    1.5对请求放行

    在默认情况下,Spring Security要求所有的请求都是必须先登录才允许访问的,可以通过Spring Security的配置类对请求放行,即不需要登录即可直接访问。

    具体的做法:

    • 使得当前SecurityConfiguration继承自WebSecurityConfigurerAdapter
    • 重写void configure(HttpSecurity http)方法,对特定的请求路径进行访问
    1. package cn.tedu.csmall.passport.config;
    2. import lombok.extern.slf4j.Slf4j;
    3. import org.springframework.context.annotation.Bean;
    4. import org.springframework.context.annotation.Configuration;
    5. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    6. import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
    7. import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    8. import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    9. import org.springframework.security.crypto.password.PasswordEncoder;
    10. @Slf4j
    11. @Configuration
    12. public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    13. @Bean
    14. public PasswordEncoder passwordEncoder() {
    15. log.debug("创建密码编码器:BCryptPasswordEncoder");
    16. return new BCryptPasswordEncoder();
    17. }
    18. @Override
    19. protected void configure(HttpSecurity http) throws Exception {
    20. http.authorizeRequests() // 要求请求必须被授权
    21. .antMatchers("/**") // 匹配一些路径
    22. .permitAll() // 允许访问
    23. .anyRequest() // 除以上配置以外的请求
    24. .authenticated(); // 经过认证的
    25. }
    26. }

    完成后,重启项目,各页面均可直接访问,不再要求登录!

    注意:此时,任何跨域的异步请求不允许提交,否则将出现403错误。

    接下来,还需要在以上配置方法中添加:

    http.csrf().disable(); // 禁用防止伪造跨域攻击
    

    如果没有以上配置,则所有的异步跨域访问(无论是否是伪造的攻击)都会被禁止,也就出现了403错误。

    1.6使用数据库中的用户名和密码

    使用Spring Security时,应该自定义类,实现UserDetailsService接口,在此接口中,有UserDetails loadUserByUsername(String username)方法,Spring Security会自动使用登录时输入的用户名来调用此方法,此方法返回的结果中应该包含与用户名匹配的相关信息,例如密码等,接下来,Spring Security会自动使用自动装配的密码编码器对密码进行验证。

    所以,应该先将“允许访问的路径”进行调整,然后,自定义类实现以上接口,并重写接口中的方法。

    关于“允许访问的路径”,可以将“Knife4j的API文档”的相关路径全部设置为允许直接访问(不需要登录),并且,开启表单验证(使得未授权请求会自动重定向到登录表单),则配置为:

    1. @Override
    2. protected void configure(HttpSecurity http) throws Exception {
    3. // 请求路径白名单
    4. String[] urls = {
    5. "/favicon.ico",
    6. "/doc.html",
    7. "/**/*.js",
    8. "/**/*.css",
    9. "/swagger-resources/**",
    10. "/v2/api-docs"
    11. };
    12. http.csrf().disable(); // 禁用防止伪造跨域攻击
    13. http.authorizeRequests() // 要求请求必须被授权
    14. .antMatchers(urls) // 匹配一些路径
    15. .permitAll() // 允许访问
    16. .anyRequest() // 除以上配置以外的请求
    17. .authenticated(); // 经过认证的
    18. http.formLogin(); // 启用登录表单,未授权的请求均会重定向到登录表单
    19. }

    关于自定义的UserDetailsService接口的实现类:

    1. package cn.tedu.csmall.passport.security;
    2. import org.springframework.security.core.userdetails.User;
    3. import org.springframework.security.core.userdetails.UserDetails;
    4. import org.springframework.security.core.userdetails.UserDetailsService;
    5. import org.springframework.security.core.userdetails.UsernameNotFoundException;
    6. import org.springframework.stereotype.Service;
    7. @Service
    8. public class UserDetailsServiceImpl implements UserDetailsService {
    9. @Override
    10. public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
    11. // 假设root是可用的用户名,其它用户名均不可用
    12. if ("root".equals(s)) {
    13. // 返回模拟的root用户信息
    14. UserDetails userDetails = User.builder()
    15. .username("root")
    16. .password("$2a$10$oxvr08D3W0oiesfGPZ8miuPy6kWGst6lz3.qZ29upo8yTjROWh4eC")
    17. .accountExpired(false) // 账号是否已经过期
    18. .accountLocked(false) // 账号是否已经锁定
    19. .credentialsExpired(false) // 认证是否已经过期
    20. .disabled(false) // 是否已经禁用
    21. .authorities("这是临时使用的且无意义的权限值") // 权限,注意,此方法的参数值不可以为null
    22. .build();
    23. return userDetails;
    24. }
    25. throw new UsernameNotFoundException("登录失败,用户名不存在!");
    26. }
    27. }

    完成后,重启项目,在启动日志将不会再出现随机的默认密码,并且,可以根据以上方法实现时的用户名+密码实现登录,如果使用错误的用户名或密码,将会提示对应的错误!

    接下来,只需要保证以上方法中返回UserDetails是基于数据库查询来返回结果即可。

    则需要:

    • 在根包下创建pojo.vo.AdminLoginInfoVO,至少包含:idusernamepasswordenable
      • 还应该查询出此用户名对应的管理员的权限,但此部分暂不实现
    • AdminMapper接口中添加抽象方法:AdminLoginInfoVO getLoginInfoByUsername(String username);
    • AdminMapper.xml中配置以上抽象方法映射的SQL语句
    • AdminMapperTests中编写并执行测试
    • UserDetailsServiceImpl中的loadUserByUsername()方法中通过以上查询来返回结果

    关于AdminMapper.xml

    1. <select id="getLoginInfoByUsername" resultMap="LoginResultMap">
    2. SELECT
    3. <include refid="LoginQueryFields"/>
    4. FROM
    5. ams_admin
    6. WHERE
    7. username=#{username}
    8. select>
    9. <sql id="LoginQueryFields">
    10. <if test="true">
    11. id, username, password, enable
    12. if>
    13. sql>
    14. <resultMap id="LoginResultMap" type="cn.tedu.csmall.passport.pojo.vo.AdminLoginInfoVO">
    15. <id column="id" property="id" />
    16. <result column="username" property="username" />
    17. <result column="password" property="password" />
    18. <result column="enable" property="enable" />
    19. resultMap>

    关于UserDetailsServiceImpl

    1. package cn.tedu.csmall.passport.security;
    2. import cn.tedu.csmall.passport.mapper.AdminMapper;
    3. import cn.tedu.csmall.passport.pojo.vo.AdminLoginInfoVO;
    4. import lombok.extern.slf4j.Slf4j;
    5. import org.springframework.beans.factory.annotation.Autowired;
    6. import org.springframework.security.core.userdetails.User;
    7. import org.springframework.security.core.userdetails.UserDetails;
    8. import org.springframework.security.core.userdetails.UserDetailsService;
    9. import org.springframework.security.core.userdetails.UsernameNotFoundException;
    10. import org.springframework.stereotype.Service;
    11. @Slf4j
    12. @Service
    13. public class UserDetailsServiceImpl implements UserDetailsService {
    14. @Autowired
    15. private AdminMapper adminMapper;
    16. @Override
    17. public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
    18. log.debug("根据用户名【{}】从数据库查询用户信息……", s);
    19. // 调用AdminMapper对象,根据用户名(参数值)查询管理员信息
    20. AdminLoginInfoVO loginInfo = adminMapper.getLoginInfoByUsername(s);
    21. // 判断是否查询到有效结果
    22. if (loginInfo == null) {
    23. // 根据用户名没有找到任何管理员信息
    24. String message = "登录失败,用户名不存在!";
    25. log.warn(message);
    26. throw new UsernameNotFoundException(message);
    27. }
    28. // 准备返回结果
    29. log.debug("根据用户名【{}】从数据库查询到有效的用户信息:{}", s, loginInfo);
    30. UserDetails userDetails = User.builder()
    31. .username(loginInfo.getUsername())
    32. .password(loginInfo.getPassword())
    33. .accountExpired(false) // 账号是否已经过期
    34. .accountLocked(false) // 账号是否已经锁定
    35. .credentialsExpired(false) // 认证是否已经过期
    36. .disabled(loginInfo.getEnable() == 0) // 是否已经禁用
    37. .authorities("这是临时使用的且无意义的权限值") // 权限,注意,此方法的参数值不可以为null
    38. .build();
    39. log.debug("即将向Spring Security返回UserDetails:{}", userDetails);
    40. return userDetails;
    41. }
    42. }

    以上查询管理员的信息时,并没有查询出管理员对应的权限信息,应该补充查询出这部分信息。

    作业

    一:在csmall-product项目中,实现以下查询功能,需开发持久层、业务逻辑层、控制器层

    \1. 查询品牌列表

    \2. 查询相册列表 

    \3. 查询属性模板列表

    \4. 根据父级类别id查询类别列表

    \5. 根据属性模块id查询属性列表 

    \6. 根据id查询品牌详情 

    \7. 根据id查询相册详情 

    \8. 根据id查询属性详情 

    \9. 根据id查询属性模板详情 

    \10. 根据id查询类别详情 

    目标:通过Knife4j在线API文档可以执行查询请求,返回JSON格式的结果 说明:暂不考虑分页问题 提示:在Service的实现方法中,只需要直接返回Mapper的查询结果即可 提示:你需要在JsonResult类中添加一个public static JsonResult ok(T data)方法 提示:在控制器层,处理查询的请求使用@GetMapping配置路径,方法的返回值类型例如:JsonResult>

    二:在csmall-passport项目中,实现以下功能,需开发持久层、业务逻辑层、控制器层 \1. 查询角色列表 \2. 插入管理员与角色关联数据(只需要完成持久层)

    三:创建新项目,实现与csmall-passport完全相同的功能

  • 相关阅读:
    css中关于fit-content尺寸的属性
    OkHttpClient请求方式详解,及常用OkHttpUtils配置工具类
    【毕业设计】基于javaEE+SSM+MySql的BS架构微博系统设计与实现(毕业论文+程序源码)——BS架构微博系统
    使用Field_II_ver_3_24_windows_gcc工具箱实现超声波数据成像matlab仿真
    解锁区块链游戏数据解决方案
    Spring MVC基于注解的使用:JSON数据处理
    Set集合
    搞懂 冒泡排序原理 锁和事务的原理(图文并茂)
    虹科示波器 | 汽车免拆检测 | 2017款路虎发现车行驶中发动机抖动且加速无力
    多线程面试相关知识点
  • 原文地址:https://blog.csdn.net/TheNewSystrm/article/details/126184443