springsecurity实现用户登录认证快速使用示例代码(前后端分离项目)

 更新时间:2024年03月21日 09:39:47   作者:两年半的个人练习生^_^  
这篇文章主要介绍了springsecurity实现用户登录认证快速使用示例代码(前后端分离项目),本文通过示例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

ps:该文章适合未系统学习springsecurity快速使用,可以直接cv使用,只有部分源码讲解,个人觉得先会用了再深究原理

1、引入依赖

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

引入springsecurity依赖后,该依赖会自动生成默认登陆页面和登录名(user)和密码(控制台)

我们使用这个用户名和密码登陆后才可以对资源进行访问

因为项目是前后端分离项目,因此并不需要它默认生成的登录页面和默认用户名密码吗,需要查询数据库进行登录

2、创建类继承WebSecurityConfigurerAdapter

我们创建一个配置类SecurityConfig(使用@Configuration标记)并且继承WebSecurityConfigurerAdapter类,重写里面的几个配置方法即可对springsecurity进行配置

(1)重写里面的configure(HttpSecurity http)方法

跟进configure(HttpSecurity http)查看源码中的方法做了什么

我们可以看到源码中的该方法中默认对所有的请求进行拦截,并且默认生成表单登录页面,并且使用基本认证。

我们只需要重写该方法就可以自己进行配置了

.csrf().disable()
.cors()        csrf建议关闭,cors前后端分离项目建议打开
.mvcMatchers("/admin/login").anonymous()   对这个接口可以匿名访问,也就是不需要认证
.mvcMatchers("/admin/save").permitAll()    对这个接口也不做认证
.mvcMatchers("/user/save").authenticated()  对这个接口需要认证才能访问

这个.mvcMatchers的参数也可以是数组形式

.addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)

添加过滤器,这里的tokenAuthenticationFilter是我自己定义的,这个意思将自定义的这个过滤器放在UsernamePasswordAuthenticationFilter.class这个过滤器之前,这个过滤器是springsecurity提供的认证过滤器,像我们的这个tokenAuthenticationFilter是需要在认证之前进行的

.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint);

这个是添加了一个认证异常处理器authenticationEntryPoint,authenticationEntryPoint也是我们自定义的,就是当用户未经过认证时返回的结果,通常当未登录访问接口时返回给前端的异常信息就在这里定义

这样我们就大致完成了这个方法中登录认证功能的一些配置

(2)重写AuthenticationManager authenticationManagerBean()

我们需要使用他里面的方法进行登录认证,并使用@Bean标注到spring容器中

@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
    return super.authenticationManagerBean();
}

(3)密码加密工具

@Bean
public PasswordEncoder passwordEncoder(){
    return new BCryptPasswordEncoder();
}

使用这个进行密码加密,springsecurity提供了很多密码加密方法,用这个就可以,可以点进去查看PasswordEncoder这个方法,这是个接口,实现了很多加密方法

然后这样我们就大致完成了这个类的配置

如果有swagger等静态资源配置,可以重写这个方法

/**
 * 配置哪些请求不拦截
 * 排除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");
}
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private TokenAuthenticationFilter tokenAuthenticationFilter;
    @Autowired
    private AuthenticationEntryPointImpl authenticationEntryPoint;
   /* @Autowired
    private UserDetailsService userDetailsService;*/
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .cors()
                .and()
                .authorizeRequests()
                .mvcMatchers("/admin/login").anonymous()
                .mvcMatchers("/admin/save").permitAll()
                .mvcMatchers("/wx/user/login").permitAll()
                .mvcMatchers("/wx/user/save").permitAll()
                .anyRequest().authenticated()
                .and()
                .addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
                .exceptionHandling()
                .authenticationEntryPoint(authenticationEntryPoint);
    }
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
    /**
     * 配置哪些请求不拦截
     * 排除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");
    }
}

防止报错还有这个自定义的登录拦截器跟认证失败处理器也整上

@Component
public class TokenAuthenticationFilter extends OncePerRequestFilter {
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Override
    protected void doFilterInternal
            (HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String token = request.getHeader("token");
        if (ObjectUtils.isEmpty(token)){
            filterChain.doFilter(request,response);
            return;
        }
        Claims claims = null;
        try {
            claims = JwtUtil.parseJWT(token);
        } catch (Exception e) {
            e.printStackTrace();
            Map<String, String> errMsg = new HashMap<>();
            errMsg.put("code","200");
            errMsg.put("msg","访问失败,请重新登录");
            response.setContentType("text/json;charset=utf-8");
            response.getWriter().print(errMsg.toString());
            return;
        }
        Integer userId = Integer.valueOf(claims.getSubject());
        UserContext.setUser(userId);
        String userAdmin = redisTemplate.opsForValue().get("userId" + userId);
        AdminLogin adminLogin = JSONUtil.toBean(userAdmin, AdminLogin.class);
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(adminLogin.getUsername(), adminLogin.getUsername(), null);
//        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(null, null, null);
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        filterChain.doFilter(request,response);
    }
}
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable
{
    private static final long serialVersionUID = -8970718410437077606L;
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e)
            throws IOException
    {
        Map<String, String> errMsg = new HashMap<>();
        response.setContentType("text/json;charset=utf-8");
        errMsg.put("code","200");
        errMsg.put("msg","访问失败,该资源受到保护...");
        response.getWriter().print(errMsg.toString());
    }
}

等会再说这两个配置

3、继承UserDetails

用我们的登录的用户类继承UserDetails,我这里是Admin

@Data
@AllArgsConstructor
@NoArgsConstructor
public class AdminLogin implements UserDetails {
    private Admin admin;
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }
    @Override
    public String getPassword() {
        return admin.getPassword();
    }
    @Override
    public String getUsername() {
        return admin.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;
    }
}

我们得重写里面的几个方法,并且把我们的用户类给整进来

Collection<? extends GrantedAuthority> getAuthorities()

这个是权限,我们返回null就行了,这会登录用不到

public String getPassword() {return admin.getPassword();}

这个方法是获取密码,也就是security会从这里获取登录的密码,我们就把我们的用户类的密码让他返回

public String getUsername() { return admin.getUsername();}

这个是获取用户名的方法,也就是security会从这里获取登录的用户名,我们就把我们的用户类的用户名让他返回

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

这几个都是账号相关的,什么账号是否被锁定、是否启用在这里返回结果,我们返回true,如果返回false就登录不了了

4、登录方法

@Service
public class AdminLoginServiceImpl implements AdminLoginService {
    @Autowired
    private AuthenticationManager authenticationManager;
    //管理员登录
    @Override
    public Result adminLogin(LoginDto loginDto) {
        UsernamePasswordAuthenticationToken authenticationToken = new
                UsernamePasswordAuthenticationToken(loginDto.getUsername(), loginDto.getPassword());
        Authentication authenticate = authenticationManager.authenticate(authenticationToken);
        AdminLogin adminLogin = (AdminLogin) authenticate.getPrincipal();
        String jwt = JwtUtil.createJWT(String.valueOf(adminLogin.getAdmin().getId()));
        //用户信息
        redisTemplate.opsForValue().set("userId"+adminLogin.getAdmin().getId(), JSONUtil.toJsonStr(adminLogin));
        return Result.success(jwt);
    }
}

登录的方法就是调用

Authentication authenticate = authenticationManager.authenticate(authenticationToken);

它里面需要接受的参数类型必须是Authentication类型的

这就是为啥在配置的时候将

AuthenticationManager authenticationManagerBean()这个使用@Bean标记

 UsernamePasswordAuthenticationToken authenticationToken = new
                UsernamePasswordAuthenticationToken(loginDto.getUsername(),loginDto.getPassword());

这个类就可以将我们的用户名和密码封装成继承了Authentication类型的类然后用于登录

UsernamePasswordAuthenticationToken()他的参数是

Object principal, Object credentials

这就分别是用户名和登陆凭证也就是密码

然后登陆成功后返回一个Authentication类型,然后.getPrincipal()这个方法就可以获取登录的用户信息。

5、是怎么完成登录的

这时候我们来看登录流程图(图是盗的)

当我们调用Authentication authenticate = authenticationManager.authenticate(authenticationToken);这个方法的时候做了什么?

我们关注两步就可以了

第一个就是根据用户名查询用户,第二个就是进行密码比对

(1)根据用户名查询用户

因为在执行认证的方法后,会调用DaoAuthencationProvider中的UserDetailService对象中的loadUserByUsername这个方法,如果基于springsecurity的默认配置,这个方法就是实现了UserDetailService这个接口的InMemoryUserDetailsManager这个方法中的loadUserByUsername

我们可以看到进行登录时候调用了loadUserByUsername的方法,这个方法是在

UserDetailsService中写的,看方法名也知道是根据用户名查找用户

因为我们要查的是数据库中的用户数据,我们就可以也可以实现UserDetailService并且重写里面的loadUserByUsername方法   根据数据库查询出用户信息并返回继承了UserDetail的AdminLogin 类

@Service
public class AdminDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private AdminService adminService;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //根据用户名查询数据库中的用户
        LambdaQueryWrapper<Admin> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Admin::getUsername,username);
        Admin admin = adminService.getOne(wrapper);
        //如果根据用户名查找不到用户
        if (ObjectUtils.isEmpty(admin)){
            throw new TxdException(208,"用户不存在");
        }
        //返回adminLogin
        AdminLogin adminLogin = new AdminLogin(admin);
        return adminLogin;
    }
}

如果能查到用户就返回,然后进行下一步密码对比,如果用户不存在就抛异常

(2)密码对比

这个springsecurity都已经写好了,我们看看源码就行,找到那个我们自定义的security的配置类

ctrl点进BCryptPasswordEncoder加密方式

里面的boolean matches(CharSequence rawPassword, String encodedPassword)这个方法就是密码对比,它里面又会执行BCrypt.checkpw(rawPassword.toString(), encodedPassword)这个方法。总之就是将前端传来的密码进行加密后与数据库的进行对比

为啥不能将数据库的密码解析后对比传来的明文密码呢?因为他这个加密之后是不可逆的

然后到这登录基本就完事了

新问题,数据库中还没加密后的用户数据怎么办?

6、注册用户加密密码

将前端传来的密码使用security配置类中的加密方式加密后就行

7、登录过滤器

在前面配置的时候已经整过代码了,在这里获取token并校验,校验完之后获取里面的用户id,根据用户id获取redis里面的数据,并将用户信息使用UsernamePasswordAuthenticationToken 封装并且放入SecurityContextHolder.getContext().setAuthentication(authenticationToken);中

@Component
public class TokenAuthenticationFilter extends OncePerRequestFilter {
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Override
    protected void doFilterInternal
            (HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String token = request.getHeader("token");
        if (ObjectUtils.isEmpty(token)){
            filterChain.doFilter(request,response);
            return;
        }
        Claims claims = null;
        try {
            claims = JwtUtil.parseJWT(token);
        } catch (Exception e) {
            e.printStackTrace();
            Map<String, String> errMsg = new HashMap<>();
            errMsg.put("code","200");
            errMsg.put("msg","访问失败,请重新登录");
            response.setContentType("text/json;charset=utf-8");
            response.getWriter().print(errMsg.toString());
            return;
        }
        Integer userId = Integer.valueOf(claims.getSubject());
        UserContext.setUser(userId);
        String userAdmin = redisTemplate.opsForValue().get("userId" + userId);
        AdminLogin adminLogin = JSONUtil.toBean(userAdmin, AdminLogin.class);
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(adminLogin.getUsername(), adminLogin.getUsername(), null);
//        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(null, null, null);
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        filterChain.doFilter(request,response);
    }
}

8、认证失败处理器

当用户未认证时访问资源提示的信息

@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable
{
    private static final long serialVersionUID = -8970718410437077606L;
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e)
            throws IOException
    {
        Map<String, String> errMsg = new HashMap<>();
        response.setContentType("text/json;charset=utf-8");
        errMsg.put("code","200");
        errMsg.put("msg","访问失败,该资源受到保护...");
        response.getWriter().print(errMsg.toString());
    }
}

到此这篇关于springsecurity实现用户登录认证快速使用(前后端分离项目)的文章就介绍到这了,更多相关springsecurity用户登录认证内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • javaweb中Filter(过滤器)的常见应用

    javaweb中Filter(过滤器)的常见应用

    这篇文章主要介绍了javaweb中Filter的常见应用,过滤器的使用方法,感兴趣的小伙伴们可以参考一下
    2015-12-12
  • 教你用Java在个人电脑上实现微信扫码支付

    教你用Java在个人电脑上实现微信扫码支付

    今天给大家带来的是Java实战的相关知识,文章围绕着Java在个人电脑上实现微信扫码支付展开,文中有非常详细的介绍及代码示例,需要的朋友可以参考下
    2021-06-06
  • Java swing框架实现的贪吃蛇游戏完整示例

    Java swing框架实现的贪吃蛇游戏完整示例

    这篇文章主要介绍了Java swing框架实现的贪吃蛇游戏,结合完整实例形式分析了java使用swing框架结合awt图形绘制实现贪吃蛇游戏的具体步骤与相关实现技巧,需要的朋友可以参考下
    2017-12-12
  • Spring Cloud Zuul路由网关服务过滤实现代码

    Spring Cloud Zuul路由网关服务过滤实现代码

    这篇文章主要介绍了Spring Cloud Zuul路由网关服务过滤实现代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-04-04
  • SpringCloud OpenFeign概述与使用

    SpringCloud OpenFeign概述与使用

    OpenFeign源于Netflix的Feign,是http通信的客户端。屏蔽了网络通信的细节,直接面向接口的方式开发,让开发者感知不到网络通信细节。所有远程调用,都像调用本地方法一样完成
    2023-01-01
  • SpringBoot实现动态配置及项目打包部署上线功能

    SpringBoot实现动态配置及项目打包部署上线功能

    本文讲解的是如何使用Spring动态配置文件,实现不同环境不同配置,灵活切换配置文件;以及讲述了如何使用 Maven 打包,然后上传至Linux服务器进行部署,对SpringBoot打包部署上线过程感兴趣的朋友一起看看吧
    2022-10-10
  • 一篇文章带你入门java代理模式

    一篇文章带你入门java代理模式

    这篇文章主要介绍了Java代理模式,结合实例形式详细分析了java基本数据类型、数据类型转换、算术运算符、逻辑运算符等相关原理与操作技巧,需要的朋友可以参考下
    2021-08-08
  • SpringBoot内存数据导出成Excel的实现方法

    SpringBoot内存数据导出成Excel的实现方法

    这篇文章主要给大家介绍了关于SpringBoot内存数据导出成Excel的实现方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-12-12
  • Spring Boot 项目中整合 MyBatis 和 PageHelper的基本步骤

    Spring Boot 项目中整合 MyBatis 和 PageHel

    这篇文章主要介绍了Spring Boot 项目中整合 MyBatis 和 PageHelper的操作步骤,整合 PageHelper 到 Spring Boot 项目中主要包括添加依赖、配置数据源与 MyBatis、配置 PageHelper 以及在业务逻辑中使用 PageHelper 进行分页查询,需要的朋友可以参考下
    2024-04-04
  • 详细说明关于Java的数据库连接(JDBC)

    详细说明关于Java的数据库连接(JDBC)

    这篇文章主要介绍了详细说明关于Java的数据库连接JDBC,JDBC是用Java语言向数据库发送SQL语句,需要的朋友可以参考下面文章内容
    2021-09-09

最新评论