【记录笔记,大佬勿喷】 关于SpringSecurity的登录流程,结合尚硅谷以及若依的项目分析

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


SpringSecurity概述

Spring Security基于Spring框架,提供了一套Web应用安全性的完整解决方案。

Web应用的安全性包括用户认证(Authentication) 和 用户授权(Authonzation),这两点也是Spring Security重要核心功能。

用户认证:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码,系统通过校验用户名和密码来完成认证过程。通俗点说就是系统认为用户是否能登录。

用户授权:验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。通俗点说就是系统判断用户是否有权限去做某些事情。
简单来说 Spring Security 本质是一个过滤器链。Spring Security在启动时会加入很多的过滤器。


提示:以下是本篇文章正文内容,下面案例可供参考

一、SpringSecurity登录流程

示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。

二、入门案例

1.引入依赖

代码如下(示例):

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

2.添加配置类

代码如下(示例):


```java
package com.jerry.security.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;

/**
 * ClassName: WebSecurityConfig
 * Package: com.jerry.security.config
 * Description:
 *
 * @Author jerry_jy
 * @Create 2023-03-03 13:44
 * @Version 1.0
 */

@Configuration
@EnableWebSecurity  //@EnableWebSecurity是开启SpringSecurity的默认行为
public class WebSecurityConfig {



}

3. 编写测试的控制器以及查看结果

@RestController
public class TestController {

    @GetMapping("/add")
    public String add() {
        return "hello security";
    }
}

启动该项目,在启动时会在控制台打印一句话:

Using generated security password:
9042005d-094d-45a1-87ef-a840378f5e76

此时直接在浏览器访问:http://localhost:8800/add ,并不会直接返回预期的字符串,而是跳转到了一个登录页面。
在这里插入图片描述

spring security登录页面的默认用户名为 user,密码为控制台打印出来的那句UUID。

登录之后即可看到 hello security。


三、尚硅谷篇

正式使用其实有多种方法,总体流程是不会变的,结合自身情况选择最合适的即可。这里演示一个尚硅谷的和一个若依的。

1、编写自定义组件以及用户数据的准备

UserDetailsService接口,UserDetails主要是封装了一些用户数据,如果默认封装的不够用可以自己拓展直接继承User类,User类默认也是实现了UserDetailsService接口。
在这里插入图片描述

以下是自己拓展UserDetails:

public class CustomUser extends User {
    /**
     * 我们自己的用户实体对象,要调取用户信息时直接获取这个实体对象。(这里我就不写get/set方法了)
     */
    private SysUser sysUser;

    public CustomUser(SysUser sysUser, Collection<? extends GrantedAuthority> authorities) {
        super(sysUser.getUsername(), sysUser.getPassword(), authorities);
        this.sysUser = sysUser;
    }

    public SysUser getSysUser() {
        return sysUser;
    }

    public void setSysUser(SysUser sysUser) {
        this.sysUser = sysUser;
    }
}
public interface UserDetailsService {
    /**
     * 根据用户名获取用户对象(获取不到直接抛异常)
     */
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

实现类:

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private SysUserService sysUserService;

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

        // 根据用户名查询
        SysUser sysUser = sysUserService.getUserByUserName(username);
        if(null == sysUser) {
            throw new UsernameNotFoundException("用户名不存在!");
        }

        if(sysUser.getStatus().intValue() == 0) {
            throw new RuntimeException("账号已停用");
        }
        return new CustomUser(sysUser, Collections.emptyList());
    }
}

SysUserService

 SysUser getUserByUserName(String username);

SysUserServiceImpl

  // 根据用户名查询
    @Override
    public SysUser getUserByUserName(String username) {
        LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SysUser::getUsername,username);

        SysUser sysUser = baseMapper.selectOne(queryWrapper);
        return sysUser;
    }

自定义密码校验器

@Component
public class CustomMd5PasswordEncoder implements PasswordEncoder {
    public String encode(CharSequence rawPassword) {
        return MD5.encrypt(rawPassword.toString());
    }

    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        return encodedPassword.equals(MD5.encrypt(rawPassword.toString()));
    }
}

2、自定义用户认证过滤器

public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {

    // 构造方法
    public TokenLoginFilter(AuthenticationManager authenticationManager){
        this.setAuthenticationManager(authenticationManager);
        this.setPostOnly(false);
        //指定登录接口及提交方式,可以指定任意路径
        this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/system/index/login","POST"));
    }

    // 登录认证过程
    // 获取输入的用户名和密码,调用方法认证
    @Override
    public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
            throws AuthenticationException {
        try {
            // 获取用户信息
            LoginVo loginVo = new ObjectMapper().readValue(req.getInputStream(), LoginVo.class);

            //封装对象
            Authentication authenticationToken = new UsernamePasswordAuthenticationToken(loginVo.getUsername(), loginVo.getPassword());

            //调用方法
            return this.getAuthenticationManager().authenticate(authenticationToken);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    // 认证成功调用的方法
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
                                            Authentication auth) throws IOException, ServletException {
        // 获取当前用户
        CustomUser customUser = (CustomUser) auth.getPrincipal();

        // 生成token
        String token = JwtHelper.createToken(customUser.getSysUser().getId(), customUser.getSysUser().getUsername());

        // 返回
        Map<String, Object> map = new HashMap<>();
        map.put("token", token);
        ResponseUtil.out(response, Result.ok(map));
    }

    // 认证失败调用的方法
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
                                              AuthenticationException e) throws IOException, ServletException {

        if(e.getCause() instanceof RuntimeException) {
            ResponseUtil.out(response, Result.build(null, ResultCodeEnum.DATA_ERROR));
        } else {
            ResponseUtil.out(response, Result.build(null, ResultCodeEnum.LOGIN_AUTH));
        }
    }

}

认证过程主要是通过用户名和密码封装成Authentication对象,并使用AuthenticationManager里的authenticate方法进行用户名和密码的认证

//封装对象
 Authentication authenticationToken = new UsernamePasswordAuthenticationToken(loginVo.getUsername(), loginVo.getPassword());

调用authenticate方法进行用户名和密码的认证

this.getAuthenticationManager().authenticate(authenticationToken);

authenticate验证原理请参考:
authenticate验证账号密码过程

认证成功后进入成功的方法,获取当前用户,再根据当前用户生成一个token并返回。

 // 认证成功调用的方法
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
                                            Authentication auth) throws IOException, ServletException {
        // 获取当前用户
        CustomUser customUser = (CustomUser) auth.getPrincipal();

        // 生成token
        String token = JwtHelper.createToken(customUser.getSysUser().getId(), customUser.getSysUser().getUsername());

        // 返回
        Map<String, Object> map = new HashMap<>();
        map.put("token", token);
        ResponseUtil.out(response, Result.ok(map));
    }

认证失败就不说了直接返回失败信息即可。

3、自定义Toekn验证过滤器

public class TokenAuthenticationFilter extends OncePerRequestFilter {
    public TokenAuthenticationFilter() {
    }
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {

        logger.info("uri:"+request.getRequestURI());
        //如果是登录接口,直接放行
        if("/admin/system/index/login".equals(request.getRequestURI())) {
            chain.doFilter(request, response);
            return;
        }

        UsernamePasswordAuthenticationToken authentication = getAuthentication(request);
        if(null != authentication) {
            SecurityContextHolder.getContext().setAuthentication(authentication);
            chain.doFilter(request, response);
        } else {
            ResponseUtil.out(response, Result.build(null, ResultCodeEnum.PERMISSION));
        }
    }

    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
        // token置于header里
        String token = request.getHeader("token");
        logger.info("token:"+token);
        if (!StringUtils.isEmpty(token)) {
            String username = JwtHelper.getUsername(token);
            logger.info("username:"+username);
            if (!StringUtils.isEmpty(username)) {
                return new UsernamePasswordAuthenticationToken(username, null, Collections.emptyList());
            }
        }
        return null;
    }
}

主要思路就是先获取头部cookie里的token,先判断是否为空,不为空在使用jwt工具解析当前token获取当前用户并封装成UsernamePasswordAuthenticationToken对象,再交给SpringSecurity上下文。

4、SpringSecurity的配置信息

@Configuration
@EnableWebSecurity  //@EnableWebSecurity是开启SpringSecurity的默认行为
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService; // 装载的是 org.springframework.security.core.userdetails.UserDetailsService;

    @Autowired
    private CustomMd5PasswordEncoder customMd5PasswordEncoder;


    @Bean
    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 这是配置的关键,决定哪些接口开启防护,哪些接口绕过防护
        http
                //关闭csrf跨站请求伪造
                .csrf().disable()
                // 开启跨域以便前端调用接口
                .cors().and()
                .authorizeRequests()
                // 指定某些接口不需要通过验证即可访问。登陆接口肯定是不需要认证的
                .antMatchers("/admin/system/index/login").permitAll()
                // 这里意思是其它所有接口需要认证才能访问
                .anyRequest().authenticated()
                .and()
                //TokenAuthenticationFilter放到UsernamePasswordAuthenticationFilter的前面,这样做就是为了除了登录的时候去查询数据库外,其他时候都用token进行认证。
                .addFilterBefore(new TokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
                .addFilter(new TokenLoginFilter(authenticationManager()));

        //禁用session
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 指定UserDetailService和加密器
        auth.userDetailsService(userDetailsService)
                .passwordEncoder(customMd5PasswordEncoder);
    }

    /**
     * 配置哪些请求不拦截
     * 排除swagger相关请求
     *
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/favicon.ico", "/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**", "/doc.html");
    }
}

5、用户权限控制

在这里插入图片描述

权限控制的主要思路就是在UserDetailsServiceImpl中重loadUserByUsername时,不仅通过用户名要查询到用户信息,还要将权限信息一块查询出来并返回。并在登录认证的过滤器当中(TokenLoginFilter),将权限信息拿出来放到Redis里(key为当前用户,value为查出来的权限信息)。在Token校验过滤器中通过解析token得到当前用户,并通过当前用户从redis里拿取当前权限信息

修改UserDetailsServiceImpl中的loadUserByUsername
在这里插入图片描述

 // 根据 user_id 查询用户操作权限数据
        List<String> userPermsList = sysMenuService.findUserPermsByUserId(sysUser.getId());
        // 创建list集合,封装最终权限数据
        List<SimpleGrantedAuthority> authList =  new ArrayList<>();
        // 遍历 authList
        for (String perms : userPermsList) {
            authList.add(new SimpleGrantedAuthority(perms.trim()));
        }

        return new CustomUser(sysUser, authList);

修改TokenLoginFilter(登录认证过滤器)
在这里插入图片描述
在这里插入图片描述
修改TokenAuthenticationFilter(Token校验过滤器)
在这里插入图片描述
在这里插入图片描述
修改WebSecurityConfig

配置类添加注解:

Spring Security默认是禁用注解的,要想开启注解,需要在继承WebSecurityConfigurerAdapter的类上加@EnableGlobalMethodSecurity注解,来判断用户对某个控制层的方法是否具有访问权限。

开启基于方法的安全认证机制,也就是说在web层的controller启用注解机制的安全确认
在这里插入图片描述
在这里插入图片描述

6、Controller层权限注解的应用

  @PreAuthorize("hasAuthority('bnt.sysRole.list')")

controller层中加上这个注解即可判断该接口是否有权限访问,bnt.sysRole.list这些权限字符串都是通过数据库查出来的,再重写loadByUserName中就已经查出并返回了。

7、总结

1、先准备用户数据,使用userDeatils封装好用户数据,觉得不够用的话直接继承User自己拓展。
2、实现UserDetailsService接口并重写 loadUserByUsername方法,通过用户名查询用户信息,然后实现基本的用户信息校验逻辑,权限信息也一起查出来并返回。
3、准备登录认证过滤器或者直接从登录控制器中自己写登录逻辑,登录逻辑主要是认证账号密码,通过UsernamePasswordAuthenticationToken传入账号密码封装成 Authentication对象,并调用AuthenticationManager里的authenticate方法实现账号密码的认证。如果认证成功再通过Authentication对象的getPrincipal()方法获取当前用户信息,在使用redis将用户名当做key,根据当前用户信息获得的权限字符串当做Value存起来。最后使用jwt工具根据用户名信息生成token返回给前端。
4、token的验证主要是靠Token校验器,通过获取头部的cookie信息得到token并用jwt解析token得到当前用户信息,得到用户名在通过用户名从redis里拿到权限信息
5、最后就是配置springSecurity信息了,WebSecurityConfig 。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Security是一个在应用程序中为认证和授权提供安全性的框架。在使用Spring Security验证登录时,可以通过配置认证提供程序、授权机制、用户角色等实现登录,以确保应用程序或系统的安全性。 以下是一个Spring Security登录验证的案例: 1. 首先,在项目的pom.xml文件中添加Spring Security的依赖。 2. 在web.xml文件中添加一个Filter,该Filter用于拦截所有请求并启用Spring Security验证。 3. 在Spring的配置文件中配置Spring Security,包括认证提供程序、授权机制和用户角色等。 4. 在页面中添加登录表单,并使用Spring Security提供的标签进行验证,如: <form:form method="post" action="/login"> <table> <tr> <td>User Name:</td> <td><form:input path="username" /></td> </tr> <tr> <td>Password:</td> <td><form:password path="password" /></td> </tr> <tr> <td></td> <td><input type="submit" value="Login" /></td> </tr> </table> </form:form> 5. 在控制器中添加登录处理逻辑,并使用Spring Security提供的接口进行验证,如: @RequestMapping(value = "/login", method = RequestMethod.POST) public String login(@ModelAttribute("user") User user, BindingResult result, HttpServletRequest request, Model model) { UserDetails details = userDetailsService.loadUserByUsername(user.getUsername()); UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(details.getUsername(), user.getPassword(), details.getAuthorities()); try { Authentication auth = authenticationManager.authenticate(token); SecurityContextHolder.getContext().setAuthentication(auth); HttpSession session = request.getSession(true); session.setAttribute("SPRING_SECURITY_CONTEXT", SecurityContextHolder.getContext()); return "redirect:/home"; } catch (AuthenticationException ex) { result.rejectValue("username", "invalidUserNameOrPassword"); return "login"; } } 以上就是一个简单的Spring Security登录验证案例,通过Spring Security的安全机制,可以为Web应用程序或系统提供可靠的登录验证机制,从而保证用户信息的安全性和可靠性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值