SpringSecurity6.0自定义数据库登录认证详细注释与两个关键点

阅读提醒

本文是基于Springboot3.0.1和Springsecurity6.0版本,阅读时请注意。

前言

Springboot升级到3.0以后,认证与授权SpringSecurity也就升到6.0了,有些写法已经跟以前的版本不太一样了。对于老手不适合阅读本文,对你没有什么帮助,但对于新手来说还是很有指导意义。

两个关键点

1.重写安全过虑配置

新版本已经放弃了WebSeucrityConfigurerAdapter,不能再用老版本的继承方法了。新版本采用的是重写SecurityFilterChain,代码如下:

package security.config;

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.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception{
        http
                .authorizeHttpRequests((authorize)->authorize
                        .requestMatchers("/login-form","/login-process").permitAll()//无需授权即可访问的url,多个地址可以这样写。
                        .requestMatchers("/company").permitAll()//也可以写一个地址。
                        .anyRequest().authenticated())//其它页面需要授权才可以访问。
                .formLogin(form->form
                        .loginPage("/login-form")//自定义的表单,可以不用框架给的默认表单。
                        .loginProcessingUrl("/login.do")//这个地址就是摆设,给外人看的,只要跟form表单的action一致就好,真正起作用的还是UserDetailsService。
                        .permitAll()
                        //.successForwardUrl("/"))
                        .defaultSuccessUrl("/"))//建议用这个,successForwardUrl只能返回指定页,而这个可以返回来源页,没有来源页才会返回指定页,体验较好。
                .logout()//注销登录
                .logoutUrl("/logout")//这个地址也只是给人看的,只要跟前端注销地址一致就可。
                .logoutRequestMatcher(new AntPathRequestMatcher("/logout","GET"))//主要是指定注销时用什么请求方法,GET和POST都可以吧。
                .logoutSuccessUrl("/login-form")//注销后要跳转的页面,那个页面一定是不需要授权就可以访问的,否则会出现意外结果。一般是首页,这里随便写的一个页面。
                .permitAll()
                .and()
                .exceptionHandling().accessDeniedPage("/unAuth.html");//自定义没权限访问的提示页面。
        return http.build();
    }

    //密码加密用的,不然没法做密码比对。
    @Bean
    public PasswordEncoder getPwdEncoder(){
        return new BCryptPasswordEncoder();
    }
}

以上代码可以直接用,只要改一下里面自定义的url即可,改成你自己的。

2.重写UserDetailsService

UserDetailsServiceSpringSecurity与认证数据交互的地方,如果你不重写UserDetailsService,那么SpringSecurity认证的过程就是走的自己内部默认的那一套,只能是在application.properties的配置里写死帐号密码和角色,这显然不能用于生产环境,生产环境自然要用数据库里的数据进行用户认证和获得相关权限,代码如下:

package security.service.impl;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.Service;
import security.model.Authority;
import security.model.Role;
import security.service.UserService;

import java.util.ArrayList;
import java.util.List;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    //通过构造方法,将我们获取数据库的数据的接口服务装配到类中,供后面获取数据。
    //也可以用注解@Autowired来自动装配,只是总报提示建议用构造方法,作为有强迫症的我就用了构造方法。
    private final UserService userService;
    public UserDetailsServiceImpl(UserService userService) {
        this.userService = userService;
    }
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //查询数据库判断用户名是否存在,如果不存在抛出UsernameNotFoundException。
        //这里引用的security.model.User就是我们自己定义的实体类,与我们的数据库对应。
        List<security.model.User> users = userService.loadUserByUsername(username);
        //判断帐号是否存在,如果查不到,SpringSecurity不会让你认证通过,就是利用UsernameNotFoundException来实现的。
        //我的数据库里都没有你的帐号,怎么会让你通过呢?
        if(users.size() == 0 || username.isEmpty()){
            throw new UsernameNotFoundException("用户名不存在");
        }
        //这样就从数据库查到我们的用户信息啦,将这些信息注入我们的实体类中。
        security.model.User user = users.get(0);

        //System.out.println(username);

        //从实体类中获取用户的密码。
        String password = user.getPassword();
        //System.out.println(password);

        //收集角色和权限,一个用户可以对应多个角色,一个角色可以对应多个权限(比如访问某个菜单或方法的权限)
        //你可以定义一个角色表,一个权限表。同时要建一个用户到角色的关联表和角色到权限的关联表。这些比较简单,这里不再赘述。
        List<GrantedAuthority> grantedAuthorityList = new ArrayList<>();
        //获取用户角色。
        List<Role> roles = userService.loadRoleByUid(user.getId());
        for(Role role:roles){
            //角色前一定要加上"ROLE_"前缀,否则SpringSecurity会视为无效,它是通过这个前缀识别角色的。
            SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority("ROLE_".concat(role.getName()));
            grantedAuthorityList.add(simpleGrantedAuthority);
            //System.out.println(role.getName());
        }
        //获取用户各模块或菜单的权限。
        List<Authority> authorities = userService.loadAuthorityByUid(user.getId());
        for(Authority authority:authorities){
            //权限不需要什么前缀,比较省心。
            SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(authority.getName());
            grantedAuthorityList.add(simpleGrantedAuthority);
            //System.out.println(authority.getName());
        }

        //以下是做授未用数据库时测试用的,我是先测试通过了下面的代码,又开始连接数据库的,你可以不用理会,但也有参考价值。
        /*if(!username.eq("admin")){
            throw new UsernameNotFoundException("用户名不存在");
        }*/
        //密码一定要用BCryptPasswordEncoder加密过的,否则认证不通过,下面的乱码就是123456加密而来的,吼吼。
        //String password = "$2a$10$UUbMgKe4ozOFfcfJcC6KoOQHLixmMNF.Evx.E5/AkidUExBGXuq6m";
        //grantedAuthorityList.add(new SimpleGrantedAuthority("ROLE_ADMIN"));//一定要加"ROLE_"前缀,否则无效。
        //grantedAuthorityList.add(new SimpleGrantedAuthority("ROLE_USER"));//每个用户最低要有一个USER角色。
        //grantedAuthorityList.add(new SimpleGrantedAuthority("CART"));//这是添加权限,不需要什么前缀。

        //来啦来啦,关键的关键来啦,要把我们收集到的用户信息一股脑的捅进SpringSecurity容器里。
        //SpringSecurity会自动验证你的用户密码是否正确,如果密码不正确就会返回登录页,正确就把用户信息注入内存以备用。
        return new User(username,password, grantedAuthorityList);
    }
}

重写UserDetailsService,是架起与数据库沟通的桥梁,这里弄明白了,后面都很简单了。尤其是最后那句ruturn new User(username,password,grantedAuthorityList),简直不要太酸爽!就是它完成了最后的数据注入。切记,此处的这个User并不是我们model下自定义的那个实体类,而是SpringSecurity框架自带的,千万不要搞错了。

前端控制器示例

这些代码随便看看就好,都是测试用的,主要是看看角色和权限如何用在授权权限上。主要是注解@PreAuthorize的使用上,怎么用角色,又怎么用权限,使用的时候这两个没有太大的区别。为什么还要分角色和权限呢?主要还是为了方便管理。人可以有多个角色,一个角色可以有多个权限,管理起来非常方便。但在代码里,都是一样的认证。

package security.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import security.model.User;
import security.service.UserService;

import java.util.List;

@Controller
@EnableMethodSecurity
public class IndexController {
    //@Autowired
    //UserMapper userMapper;
    @Autowired
    UserService userService;
    @RequestMapping("/")
    public String index(){
        //UserService userService = new UserServiceImpl(userMapper);
        List<User> users = userService.loadUserByUsername("admin");
        System.out.println(users.get(0).getPassword());
        return "index";
    }
    @RequestMapping("/login-form")
    public String loginForm(){
        return "login-form";
    }
    @RequestMapping("/login-process")
    @ResponseBody
    public String loginProcess(){
        return "login-process";
    }
    @RequestMapping("/company")
    @ResponseBody
    public String company(){
        return "company";
    }
    @RequestMapping("/product")
    @ResponseBody
    @PreAuthorize("hasRole('USER')")//以角色身份访问。
    public String product(){
        return "product";
    }
    @RequestMapping("/price")
    @ResponseBody
    @PreAuthorize("hasRole('ADMIN')")//如果以角色身份访问,不需要在前面加"ROLE_"前缀。
    public String price(){
        return "price";
    }
    @RequestMapping("/cart")
    @ResponseBody
    @PreAuthorize("hasAuthority('SHEQU')")//以权限身份访问。
    public String cart(){
        return "我的购物车!";
    }
}

总结

本文切中要害,直接把安全认证的两个关键点找了出来,让大家明白自定登录我要从哪里入手。第一是重写SecurityFilterChain,第二是重写UserDetailsService,如何使用很简单,就是注解@PreAuthorize放在哪里的问题。

  • 6
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Spring Security 6.0中,权限信息的写入数据库是通过以下步骤实现的: 1. 首先,需要创建一个权限信息的数据表。可以使用Spring Security提供的默认模式(例如,使用`JdbcDaoImpl`类),或者根据业务需求自定义自己的权限信息表结构。 2. 在权限信息表中,可以定义多个字段来存储权限信息,例如权限的名称、描述、URL等。根据具体的业务需求,可以自定义表结构。 3. 在Spring Security的配置文件中,需要配置数据源(例如数据库连接池),以便能够连接到数据库。可以使用Spring提供的`DataSource`类来配置数据源。 4. 在Spring Security的配置文件中,需要配置`UserDetailsService`的具体实现类。该类用于从数据库中获取用户的权限信息。可以自定义一个实现`UserDetailsService`接口的类,并在其中实现从数据库中读取用户权限信息的逻辑。 5. 在权限管理模块中,需要添加相关代码来完成权限信息的写入数据库的功能。可以使用Spring Security提供的API来完成相应的操作,例如使用`UserDetailsManager`类和`JdbcUserDetailsManager`类来管理用户和权限等信息。 总结起来,实现权限信息的写入数据库的过程包括创建权限信息表、配置数据源、配置`UserDetailsService`的实现类,并在权限管理模块中编写相关代码来完成数据写入数据库的功能。在Spring Security 6.0中,可以根据具体的业务需求选择合适的方案来实现权限信息的写入数据库

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值