在前后端分离项目中,跨域一直是一个常常存在的事情,那么如何解决这个问题呢,看如下代码。
在springboot项目中的config目录下编写一个全局跨域配置
1:创建一个CorsConfiguration对象,并设置允许的域名、请求头和请求方法;
2:设置允许携带凭证,并添加允许的域名模式;
3:创建一个UrlBasedCorsConfigurationSource对象,并将之前配置的CorsConfiguration对象注册到该对象中;
4:最后返回一个新的CorsFilter对象,该对象使用UrlBasedCorsConfigurationSource对象作为其配置源。
5:这样配置后,该服务就能对来自任何路径的跨域请求进行过滤和处理
package com.aaaa.config;
import org.springframework.web.filter.CorsFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
/**
* @program: AAA-Springboot-Security-21
* @description: CrosConfig
* @author: 七
* @create: 2024-06-19 22:43
**/
@SuppressWarnings("all")
@Configuration
public class CrosConfig {
@Bean
public CorsFilter corsFilter() {
//跨域配置
CorsConfiguration corsConfiguration = new CorsConfiguration();
// 允许的域名,不要写*,否则cookie就无法使用了
corsConfiguration.addAllowedOrigin("http://localhost:8080");
// 允许的请求头
corsConfiguration.addAllowedHeader("*");
//请求方式
corsConfiguration.addAllowedMethod("*");
corsConfiguration.setAllowCredentials(true);
//允许携带凭证
corsConfiguration.addAllowedOriginPattern("*");
//基于请求路径的跨域配置
UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
//所有的路径都能跨域
urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
return new CorsFilter(urlBasedCorsConfigurationSource);
}
}
为了实现一个简单的前后端交互登录效果,跨域解决后,要重实现一下userdetailsService,重写一下它里面的loadbyusernam方法,在里面进行登录用户的信息查询,
这个里面的权限信息sql表在之前的security文章中有实例
1:通过@Autowired注解自动注入userMapper对象,用于执行数据库操作。
2:重写loadUserByUsername方法,接收一个用户名作为参数。
3:调用userMapper的selectEmployee方法查询该用户名对应的员工信息,并返回一个Employee对象的List。
4:调用userMapper的selectRole方法查询该员工对应的角色信息,并返回一个Role对象的List。
5:创建一个SimpleGrantedAuthority对象的ArrayList,用于存储用户的权限信息。
6:遍历Role对象的List,将每个角色的名称转换为SimpleGrantedAuthority对象,并添加到list1中。
7:创建一个Integer对象的ArrayList,用于存储角色的ID。
8:遍历Role对象的List,将每个角色的ID添加到list2中。
9:调用userMapper的selectResource方法查询与list2中的角色ID对应的资源信息,并返回一个Resource对象的List。
10:遍历Resource对象的List,将每个资源的名称转换为SimpleGrantedAuthority对象,并添加到list1中。
11:使用用户名、密码和权限信息创建一个User对象,并返回该对象作为查询结果。
package com.aaaa.service;
import com.aaaa.enity.Employee;
import com.aaaa.enity.Resource;
import com.aaaa.enity.Role;
import com.aaaa.mapper.userMapper;
import org.springframework.beans.factory.annotation.Autowired;
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 java.util.ArrayList;
import java.util.List;
/**
* @program: AAA-Springboot-Security-21
* @description:
* @author: 七
* @create: 2024-06-20 10:01
**/
@SuppressWarnings("all")
@Service
/**
* 实现UserDetailsService接口,用于加载用户详细信息。
*/
public class userDetiels implements UserDetailsService {
/**
* 自动注入用户映射器,用于数据库操作。
*/
@Autowired
userMapper userMapper;
/**
* 根据用户名加载用户详细信息。
*
* @param username 用户名
* @return UserDetails对象,包含用户信息和权限
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("username:" + username);
// 根据用户名查询员工信息
List<Employee> employees = userMapper.selectEmployee(username);
// 检查用户是否存在
if (employees.size() < 1) {
System.out.println("该用户不存在");
}
// 根据员工ID查询角色信息
List<Role> roles = userMapper.selectRole(employees.get(0).getId());
// 初始化权限列表
ArrayList<SimpleGrantedAuthority> list1 = new ArrayList<>();
// 初始化角色ID列表,用于后续查询资源权限
ArrayList<Integer> list2 = new ArrayList<>();
// 遍历角色,将角色权限添加到权限列表
for (Role role : roles) {
list1.add(new SimpleGrantedAuthority(role.getRole()));
list2.add(role.getId());
}
// 根据角色ID查询资源权限
List<Resource> resources = userMapper.selectResource(list2);
// 将资源权限添加到权限列表
for (Resource resource : resources) {
list1.add(new SimpleGrantedAuthority(resource.getResource()));
}
// 返回User对象,包含用户信息和权限
return new User(username, employees.get(0).getEmPassword(), list1);
}
}
自定义完userdetailsService之后
该代码是Spring Security的配置类,用于配置应用程序的安全性。
它继承了WebSecurityConfigurerAdapter,并覆盖了configure方法来设置认证和授权。
通过@Autowired注解自动注入UserDetailsService,用于认证过程中查询用户信息。
在configure(AuthenticationManagerBuilder auth)方法中,配置AuthenticationManagerBuilder以使用自定义的用户详情服务和密码编码器。
在configure(HttpSecurity http)方法中,配置HTTP安全设置,包括表单登录、退出登录、权限不足处理、CORS等。
通过@Override注解覆盖了WebSecurityConfigurerAdapter的其他方法,但没有对其进行修改。
该类还定义了一个printJson方法,用于将Map对象转换为JSON格式,并写入HTTP响应中。
该配置类的作用是为应用程序提供安全配置,包括认证和授权的设置。它使用了Spring Security提供的注解和接口,使得配置过程更加简单和直观。
package com.aaaa.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
/**
* @program: AAA-Springboot-Security-21
* @description: MySecurityConfig
* @author: 七
* @create: 2024-06-19 23:13
**/
@SuppressWarnings("all")
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(jsr250Enabled = true, securedEnabled = true, prePostEnabled = true)
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 自动注入UserDetailsService,用于认证过程中查询用户信息。
*/
@Autowired
UserDetailsService userDetailsService;
/**
* 配置AuthenticationManagerBuilder,以使用自定义的用户详情服务和密码编码器。
*
* @param auth AuthenticationManagerBuilder的实例,用于配置认证逻辑。
* @throws Exception 如果配置过程中出现错误。
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// PasswordEncoder passwordEncoder = getPasswordEncoder();
// auth.inMemoryAuthentication().passwordEncoder(passwordEncoder);
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
// /**
// * 提供一个@Bean注解的方法来定义一个密码编码器 bean。
// *
// * @return 返回一个BCryptPasswordEncoder实例,用于加密和验证用户密码。
// */
// @Bean
// public PasswordEncoder passwordEncoder() {
// return ;
// }
/**
* 配置HTTP安全设置,用于定制Spring Security的HTTP安全功能。
* @param http Spring Security的HTTP安全配置对象
* @throws Exception 如果配置过程中出现异常
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
// 配置表单登录
http.formLogin()
// 定义登录成功处理器
.successHandler((request, response, authentication) -> {
Map map = new HashMap();
map.put("code", 200);
map.put("msg", "登录成功");
map.put("data", authentication.getPrincipal());
printJson(response,map);
})
// 定义登录失败处理器
.failureHandler((request, response, exception) -> {
Map map = new HashMap();
map.put("code", 500);
map.put("msg", "登录失败");
map.put("data", exception.getMessage());
printJson(response,map);
})
.loginProcessingUrl("/mylogin").permitAll();
// .usernameParameter("username").passwordParameter("password");
// 配置退出登录
http.logout().logoutSuccessHandler((request, response, authentication) -> {
Map map = new HashMap();
map.put("code", 200);
map.put("msg", "退出成功");
map.put("data", authentication);
printJson(response,map);
});
// 配置权限不足时的处理
http.exceptionHandling().accessDeniedHandler((request, response, accessDeniedException) -> {
Map map = new HashMap();
map.put("code", 500);
map.put("msg", "权限不足");
map.put("data", accessDeniedException.getMessage());
printJson(response,map);
});
// 配置所有请求都需要认证
http.authorizeRequests().anyRequest().authenticated();//除了放行之外的路径,其他全部需要认证
// 关闭CSRF保护
http.csrf().disable();
// 配置CORS
http.cors();
}
/**
* 配置Web安全设置,主要处理非Spring MVC的请求。
* @param web Spring Security的Web安全配置对象
* @throws Exception 如果配置过程中出现异常
*/
/**
* 将Map对象转换为JSON格式,并写入HTTP响应中。
* @param response HTTP响应对象
* @param map 要转换为JSON的Map对象
*/
public void printJson(HttpServletResponse response, Map map) {
response.setContentType("application/json;charset=utf8");
ObjectMapper objectMapper = new ObjectMapper();
String s = null;//转化为JSON数据
PrintWriter writer = null;
try {
s = objectMapper.writeValueAsString(map);
writer = response.getWriter();
} catch (Exception e) {
throw new RuntimeException(e);
}
//给vue响应一个格式,字符编码格式是utf8
writer.print(s);
writer.flush();
writer.close();
}
}
这时候vue项目中就可以跨域访问路径了,这里的200状态就是security配置文件中返回的状态,如果认证成功就返回200,这里判断如果后端认证成功了,就在这里thi.$router.push()跳转页面,这是一个登录的操作,说白了就是后端拿到的账号密码查询成功就返回200状态码,我这里query:{list:authorlist}是将传过来的用户所拥有的资源通过路径返回了出去给其他页面
1:我这里的post请求为何就一个mylogin,是因为我的vue项目中配置全局axios, http://localhost:8080/这个前缀会在请求路径上直接拼接上去
this.$http.post(`mylogin`,qs.stringify(this.form)).then(res=>{
// alert("********走完拦截器之后走的方法******")
console.log(res)
if(res.code==200){
console.log(res.data.authorities)
var authoritylist=[]
for (var i=0;i<res.data.authorities.length;i++){
var authority= res.data.authorities[i].authority
if (authority.slice(0,3)!="VIP"){
authoritylist.push(authority)
}
}
this.$router.push({path:"/test",query:{list:authoritylist}})
}else{
console.log(res)
alert("用户名或密码错误")
}
})