SpringSecurity(一): Springboot 3.2.1 整合 SpringSecurity 完成认证

SpringSecurity(一): Springboot 3.2.1 整合 SpringSecurity 完成认证

环境说明:
SpringBoot 3.2.1 、SpringSecurity 6.1.2

认证:校验用户输入的账号信息,通过后,为其颁发“业务token”和“安全token

业务token作用:传输用户编号,并根据编号换取“安全token

步骤

1.导包

<!--springsecurity核心包-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

<!--用签发业务token包-->
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.4.0</version>
</dependency>

<!--将java对象在对象和json格式之间转换-->
<dependency>
    <groupId>com.alibaba.fastjson2</groupId>
    <artifactId>fastjson2</artifactId>
    <version>2.0.39</version>
</dependency>

2.自定义扩展用户信息校验规则

目的:将springsecurity默认的登录逻辑迁移到自定义的逻辑上(访问数据库)

/*
实现 UserDetailsService 接口,自定义校验逻辑
@Component让spring知晓自定义的内容
*/
@Component
public class SecurityUserDetailServiceImpl implements UserDetailsService {
    @Autowired
    private ShopUserService shopUserService;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //1.根据用户名查询用户信息
        QueryWrapper<ShopUser> qw = new QueryWrapper<>();
        qw.eq("user_name",username);
        ShopUser shopUser = shopUserService.getOne(qw);

        //2.TODO 查询该用户的权限列表并整合对象

        return new SecurityUserDetailsImpl(shopUser);
    }
}
/*
	因为Security内部的loadUserByUsername方法需要的返回值必须是UserDetails类型,项目本身提供的用户类型不适用
	按照java多态标准,此类符合UserDetails规范,可以用来返回
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SecurityUserDetailsImpl implements UserDetails {

    private ShopUser shopUser;

    /*
    	获取校验用户的权限列表
    */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    /*
    	获取系统用户的密码信息,用于security后续的密码对比
    */
    @Override
    public String getPassword() {
        return shopUser.getPassword();
    }
 	/*
    	获取系统用户的用户名信息
    */
    @Override
    public String getUsername() {
        return shopUser.getUserName();
    }

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

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

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

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

3.配置security框架

需要配置以下信息:

​ 1.密码解析器

​ 2.认证管理器(提供了用户信息校验逻辑)

​ 3.对security过滤器链定义

@Configuration  //标记此类为一个springboot的配置类
@EnableWebSecurity  //开启security基于web开发的安全机制
public class SecurityConfig {
    @Autowired
    private SecurityUserDetailServiceImpl securityUserDetailService;

    @Autowired
    private SecurityTokenFilter securityTokenFilter;
    /*
    密码加密器,
    	用户表中的用户密码等敏感信息都需要加密存储
     */
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    //2.配置认证管理器,security框架默认不提供
    @Bean
    public AuthenticationManager authenticationManager(){
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        //设置securityUserDetailService,告知security框架,按照指定的类进行身份校验
        daoAuthenticationProvider.setUserDetailsService(securityUserDetailService);
        ProviderManager pm = new ProviderManager(daoAuthenticationProvider);
        return pm;
    }

    //3.配置springsecurity的放行路径等信息
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception{
        //对所有请求按照以下约定进行拦截和放行
        http.authorizeHttpRequests(
            		//requestMatchers 指定匹配路径
            		//permitAll 让security跳过之前通过requestMatchers匹配到的路径,
                auth -> auth.requestMatchers("/shopUser/login").permitAll()
            		//anyRequest 指定除requestMatchers匹配路径之外的其他路径
            		//authenticated 让anyRequest匹配到的所有路径都通过security校验
                        .anyRequest().authenticated()
        );

        //关闭 防止客户端的 csrf(跨站伪造) 攻击行为 的能力
        // 从security过滤器链中撤出 CsrfFilter 
        http.csrf(csrf -> csrf.disable());

        
        //将自定义的token认证过滤器加入到security-filterChian中,并指定其位置
        http.addFilterBefore(securityTokenFilter, UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }
}

4.开放自定义的登录接口

目的:不再使用springsecurity提供的默认登陆页面,完全自定义登录逻辑

@RestController
@RequestMapping("shopUser")
public class ShopUserController{
    @Resource
    private ShopUserService shopUserService;
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private JWTUtil jwtUtil;
    @Autowired
    private SecurityInfoService securityInfoService;
    
    
    /**
     * 因为 springsecurity 框架提供的登录页面不适用于当前项目(前后端分离)
     * @param loginPo
     * @return
     */
    @RequestMapping(path = "login",method = RequestMethod.POST)
    public BaseResult login(@RequestBody LoginPo loginPo){
        //1.调用security认证方法
        //1.1 封装 token 对象
        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(loginPo.getUserName(),loginPo.getPassword());
        //1.2调用管理器的方法
        //通过认证的用户将会获得 Authentication ,其中存放用户信息
        Authentication authentication = authenticationManager.authenticate(token);
        //1.3判断结果
        // authentication 如果为null,则表示认证失败
        if(Objects.nonNull(authentication)){
            SecurityUserDetailsImpl securityUserDetails = (SecurityUserDetailsImpl)authentication.getPrincipal();
            //将security颁发的后端凭证存入数据库,此处因为暂时不涉及redis,所以json化后存入数据库
            ShopUser shopUser = securityUserDetails.getShopUser();
            securityInfoService.save(new SecurityInfo(shopUser.getId(), JSON.toJSONString(authentication)));
            //认证通过,办法业务token
            HashMap<String, String> serviceToken = jwtUtil.cerateToken(shopUser);
            return BaseResult.ok(serviceToken);
        }
        return BaseResult.error("认证失败");
    }
}

5.对其他请求的控制

思想:通过业务token中携带的用户id,换取安全token

@Component
public class SecurityTokenFilter extends OncePerRequestFilter {

    @Autowired
    private JWTUtil jwtUtil;

    @Autowired
    private SecurityInfoService securityInfoService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String token = request.getHeader("token");
        Integer id = null;
        if(token != null && !token.equals("")){
            //校验token合法性
            try{
                //从 otken 中获取指定的 payload
                DecodedJWT decodedJWT = jwtUtil.getToken(token);
                Claim jwtClaim = decodedJWT.getClaim("id");
                id = jwtClaim.asInt();
            }catch (Exception e){
                System.out.println("token 非法!");
                return;
            }
            //去数据库查询当前用户认证时生成的token
            QueryWrapper<SecurityInfo> qw = new QueryWrapper();
            qw.eq(SecurityInfo.COL_UID,id);
            SecurityInfo securityInfo = securityInfoService.getOne(qw);
            String authenticationJson = securityInfo.getAuthenticationJson();
            //将存入数据库的安全token转会后端识别的对象
            Authentication authentication = JSONObject.parseObject(authenticationJson, Authentication.class);
            //将它放入security全局上下文
            SecurityContextHolder.getContext().setAuthentication(authentication);
            //放行,请求会进入下一个过滤器
            filterChain.doFilter(request,response);
        }else{
            filterChain.doFilter(request,response);
        }
    }
}
  • 12
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值