SpringBoot Security前后端分离登录验证的实现

作者:zs319428 时间:2023-03-09 10:30:07 

最近一段时间在研究OAuth2的使用,想整个单点登录,从网上找了很多demo都没有实施成功,也许是因为压根就不懂OAuth2的原理导致。有那么几天,越来越没有头绪,又不能放弃,转过头来一想,OAuth2是在Security的基础上扩展的,对于Security自己都是一无所知,干脆就先研究一下Security吧,先把Security搭建起来,找找感觉。

说干就干,在现成的SpringBoot 2.1.4.RELEASE环境下,进行Security的使用。
简单的Security的使用就不说了,目前的项目是前后端分离的,登录成功或者失败返回的数据格式必须JSON形式的,未登录时也需要返回JSON格式的提示信息 ,退出时一样需要返回JSON格式的数据。授权先不管,先返回JSON格式的数据,这一个搞定,也研究了好几天,翻看了很多别人的经验,别人的经验有的看得懂,有的看不懂,关键时刻还需要自己研究呀。

下面,上代码:

第一步,在pom.xml中引入Security配置文件


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

第二步,增加Configuration配置文件


import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import com.fasterxml.jackson.databind.ObjectMapper;

/**
* 参考网址:
* https://blog.csdn.net/XlxfyzsFdblj/article/details/82083443
* https://blog.csdn.net/lizc_lizc/article/details/84059004
* https://blog.csdn.net/XlxfyzsFdblj/article/details/82084183
* https://blog.csdn.net/weixin_36451151/article/details/83868891
* 查找了很多文件,有用的还有有的,感谢他们的辛勤付出
* Security配置文件,项目启动时就加载了
* @author 程就人生
*
*/
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {  

@Autowired
private MyPasswordEncoder myPasswordEncoder;

@Autowired
private UserDetailsService myCustomUserService;

@Autowired
private ObjectMapper objectMapper;

@Override
protected void configure(HttpSecurity http) throws Exception {

http
 .authenticationProvider(authenticationProvider())
 .httpBasic()
 //未登录时,进行json格式的提示,很喜欢这种写法,不用单独写一个又一个的类
  .authenticationEntryPoint((request,response,authException) -> {
   response.setContentType("application/json;charset=utf-8");
   response.setStatus(HttpServletResponse.SC_FORBIDDEN);
   PrintWriter out = response.getWriter();
   Map<String,Object> map = new HashMap<String,Object>();
   map.put("code",403);
   map.put("message","未登录");
   out.write(objectMapper.writeValueAsString(map));
   out.flush();
   out.close();
  })

.and()
  .authorizeRequests()
  .anyRequest().authenticated() //必须授权才能范围

.and()
  .formLogin() //使用自带的登录
  .permitAll()
  //登录失败,返回json
  .failureHandler((request,response,ex) -> {
   response.setContentType("application/json;charset=utf-8");
   response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
   PrintWriter out = response.getWriter();
   Map<String,Object> map = new HashMap<String,Object>();
   map.put("code",401);
   if (ex instanceof UsernameNotFoundException || ex instanceof BadCredentialsException) {
    map.put("message","用户名或密码错误");
   } else if (ex instanceof DisabledException) {
    map.put("message","账户被禁用");
   } else {
    map.put("message","登录失败!");
   }
   out.write(objectMapper.writeValueAsString(map));
   out.flush();
   out.close();
  })
  //登录成功,返回json
  .successHandler((request,response,authentication) -> {
   Map<String,Object> map = new HashMap<String,Object>();
   map.put("code",200);
   map.put("message","登录成功");
   map.put("data",authentication);
   response.setContentType("application/json;charset=utf-8");
   PrintWriter out = response.getWriter();
   out.write(objectMapper.writeValueAsString(map));
   out.flush();
   out.close();
  })
  .and()
  .exceptionHandling()
  //没有权限,返回json
  .accessDeniedHandler((request,response,ex) -> {
   response.setContentType("application/json;charset=utf-8");
   response.setStatus(HttpServletResponse.SC_FORBIDDEN);
   PrintWriter out = response.getWriter();
   Map<String,Object> map = new HashMap<String,Object>();
   map.put("code",403);
   map.put("message", "权限不足");
   out.write(objectMapper.writeValueAsString(map));
   out.flush();
   out.close();
  })
  .and()
  .logout()
  //退出成功,返回json
  .logoutSuccessHandler((request,response,authentication) -> {
   Map<String,Object> map = new HashMap<String,Object>();
   map.put("code",200);
   map.put("message","退出成功");
   map.put("data",authentication);
   response.setContentType("application/json;charset=utf-8");
   PrintWriter out = response.getWriter();
   out.write(objectMapper.writeValueAsString(map));
   out.flush();
   out.close();
  })
  .permitAll();
  //开启跨域访问
  http.cors().disable();
  //开启模拟请求,比如API POST测试工具的测试,不开启时,API POST为报403错误
  http.csrf().disable();
}

@Override
public void configure(WebSecurity web) {
 //对于在header里面增加token等类似情况,放行所有OPTIONS请求。
 web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
}

@Bean
public AuthenticationProvider authenticationProvider() {
 DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
 //对默认的UserDetailsService进行覆盖
 authenticationProvider.setUserDetailsService(myCustomUserService);
 authenticationProvider.setPasswordEncoder(myPasswordEncoder);
 return authenticationProvider;
}

}

第三步,实现UserDetailsService接口


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.Component;
/**
* 登录专用类
* 自定义类,实现了UserDetailsService接口,用户登录时调用的第一类
* @author 程就人生
*
*/
@Component
public class MyCustomUserService implements UserDetailsService {

/**
 * 登陆验证时,通过username获取用户的所有权限信息
 * 并返回UserDetails放到spring的全局缓存SecurityContextHolder中,以供授权器使用
 */
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
 //在这里可以自己调用数据库,对username进行查询,看看在数据库中是否存在
 MyUserDetails myUserDetail = new MyUserDetails();
 myUserDetail.setUsername(username);
 myUserDetail.setPassword("123456");
 return myUserDetail;
}
}

说明:这个类,主要是用来接收登录传递过来的用户名,然后可以在这里扩展,查询该用户名在数据库中是否存在,不存在时,可以抛出异常。本测试为了演示,把数据写死了。

第四步,实现PasswordEncoder接口


import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
/**
* 自定义的密码加密方法,实现了PasswordEncoder接口
* @author 程就人生
*
*/
@Component
public class MyPasswordEncoder implements PasswordEncoder {

@Override
public String encode(CharSequence charSequence) {
 //加密方法可以根据自己的需要修改
 return charSequence.toString();
}

@Override
public boolean matches(CharSequence charSequence, String s) {
 return encode(charSequence).equals(s);
}
}

说明:这个类主要是对密码加密的处理,以及用户传递过来的密码和数据库密码(UserDetailsService中的密码)进行比对。

第五步,实现UserDetails接口


import java.util.Collection;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

/**
* 实现了UserDetails接口,只留必需的属性,也可添加自己需要的属性
* @author 程就人生
*
*/
@Component
public class MyUserDetails implements UserDetails {

/**
 *
 */
private static final long serialVersionUID = 1L;

//登录用户名
private String username;
//登录密码
private String password;

private Collection<? extends GrantedAuthority> authorities;

public void setUsername(String username) {
 this.username = username;
}

public void setPassword(String password) {
 this.password = password;
}

public void setAuthorities(Collection<? extends GrantedAuthority> authorities) {
 this.authorities = authorities;
}

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
 return this.authorities;
}

@Override
public String getPassword() {
 return this.password;
}

@Override
public String getUsername() {
 return this.username;
}

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

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

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

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

说明:这个类是用来存储登录成功后的用户数据,登录成功后,可以使用下列代码获取:


MyUserDetails myUserDetails= (MyUserDetails) SecurityContextHolder.getContext().getAuthentication() .getPrincipal();

代码写完了,接下来需要测试一下,经过测试才能证明代码的有效性,先用浏览器吧。

第一步测试,未登录前访问index,页面直接重定向到默认的login页面了,测试接口OK。

SpringBoot Security前后端分离登录验证的实现

图-1

第二步测试,登录login后,返回了json数据,测试结果OK。

SpringBoot Security前后端分离登录验证的实现

图-2

第三步测试,访问index,返回输出的登录数据,测试结果OK。

SpringBoot Security前后端分离登录验证的实现

图-3

第四步,访问logout,返回json数据,测试接口OK。

SpringBoot Security前后端分离登录验证的实现

图-4

第五步,用API POST测试,用这个工具模拟ajax请求,看请求结果如何,首先访问index,这个必须登录后才能访问。测试结果ok,返回了我们需要的JSON格式数据。

SpringBoot Security前后端分离登录验证的实现

图-5

第六步,在登录模拟对话框,设置环境变量,以保持登录状态。

SpringBoot Security前后端分离登录验证的实现

图-6

**第七步,登录测试,返回JSON格式的数据,测试结果OK。

SpringBoot Security前后端分离登录验证的实现

图-7

第八步,在返回到index测试窗口,发送请求,返回当前用户JSON格式的信息,测试结果OK。

SpringBoot Security前后端分离登录验证的实现

图-8

第九步,测试退出,返回JSON格式数据,测试结果OK

SpringBoot Security前后端分离登录验证的实现

图-9

第十步,退出后,再访问index,出现问题,登录信息还在,LOOK!

SpringBoot Security前后端分离登录验证的实现

图-10

把头部的header前面的勾去掉,也就是去掉cookie,这时正常了,原因很简单,在退出时,没有清除cookie,这个只能到正式的环境上去测了。API POST再怎么模拟还是和正式环境有区别的。

如果在API POST测试报403错误,那就需要把configuration配置文件里的


//开启跨域访问
http.cors().disable();
//开启模拟请求,比如API POST测试工具的测试,不开启时,API POST为报403错误
http.csrf().disable();

来源:https://blog.csdn.net/zs319428/article/details/107089473

标签:SpringBoot,Security,登录验证
0
投稿

猜你喜欢

  • 一文详解Java拦截器与过滤器的使用

    2021-12-17 03:40:41
  • Netty分布式从recycler对象回收站获取对象过程剖析

    2021-08-12 06:45:48
  • 使用mybatis-plus-generator进行代码自动生成的方法

    2021-09-03 00:50:18
  • 详解Java之冒泡排序与选择排序

    2021-11-06 12:49:24
  • Java中BigDecimal类的使用详解

    2021-12-13 05:30:52
  • 使用C#编写15子游戏

    2023-03-23 08:28:52
  • C#中线程同步对象的方法分析

    2021-06-06 05:43:46
  • Java GUI编程实现在线聊天室

    2022-04-07 22:50:09
  • SpringCloud如何使用Eureka实现服务之间的传递数据

    2022-02-17 18:47:37
  • Android动画之补间动画(Tween Animation)实例详解

    2023-10-18 09:40:53
  • MyBatis关闭一级缓存的两种方式(分注解和xml两种方式)

    2023-09-02 10:18:59
  • 判断一个整数是否是2的N次幂实现方法

    2022-12-25 00:55:10
  • MyBatis Plus插件机制与执行流程原理分析详解

    2021-10-09 20:53:48
  • C#中事件处理的个人体会

    2023-06-22 16:50:55
  • c# 利用易福门振动模块VSE002采集振动数据的方法

    2022-10-13 00:12:25
  • Java集合系列之ArrayList源码分析

    2023-01-31 03:02:36
  • C#导出生成excel文件的方法小结(xml,html方式)

    2023-10-03 16:32:26
  • Javassist用法详解

    2023-06-26 17:11:54
  • C++深入探究引用的使用

    2023-04-28 18:02:03
  • Android编程基于Contacts读取联系人的方法(附demo源码)

    2023-11-22 06:05:39
  • asp之家 软件编程 m.aspxhome.com