Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
Spring Security 是 Spring 家族中的一個安全管理框架,可以和Spring Boot項目很方便的集成。Spring Security框架的兩大核心功能:認證和授權(quán)
認證: 驗證當前訪問系統(tǒng)的是不是本系統(tǒng)的用戶,并且要確認具體是哪個用戶。簡單的理解就是登陸操作,如果可以登錄成功就說明您是本系統(tǒng)的用戶,如不能登錄就說明不是本系
統(tǒng)的用戶!而且登錄成功以后需要記錄當前登錄用戶的信息!
授權(quán):經(jīng)過認證后判斷當前用戶是否有權(quán)限進行某個操作!
如上圖所示就是展示了當前登錄用戶可以操作的權(quán)限:用戶管理、角色管理、菜單管理等,并且針對角色管理可以進行新增、修改、刪除、導出等權(quán)限。
而現(xiàn)在前后端分離開發(fā)成為了主流的開發(fā)方式,那么在前后端分離開發(fā)方式下如何使用Spring Security就是本文章需要重點研究的內(nèi)容。
要想了解如果使用Spring Security進行認證,那么就需要先了解一下前后端分離項目中的認證流程,如下所示:
要想使用Spring Security框架來實現(xiàn)上述的認證操作,就必須先要了解一個Spring Security框架的工作流程。
Spring Security的原理其實就是一個過濾器鏈,內(nèi)部包含了提供各種功能的過濾器。這里我們可以看看入門案例中的過濾器。
圖中只展示了核心過濾器,其它的非核心過濾器并沒有在圖中展示。
UsernamePasswordAuthenticationFilter: 負責處理我們在登陸頁面填寫了用戶名密碼后的登陸請求。
ExceptionTranslationFilter:處理過濾器鏈中拋出的任何AccessDeniedException和AuthenticationException 。
FilterSecurityInterceptor:負責權(quán)限校驗的過濾器。
Spring Security的認證流程大致如下所示:
概念速查:
Authentication接口: 它的實現(xiàn)類,表示當前訪問系統(tǒng)的用戶,封裝了用戶相關(guān)信息。
AuthenticationManager接口:定義了認證Authentication的方法
UserDetailsService接口:加載用戶特定數(shù)據(jù)的核心接口。里面定義了一個根據(jù)用戶名查詢用戶信息的方法。
UserDetails接口:提供核心用戶信息。通過UserDetailsService根據(jù)用戶名獲取處理的用戶信息要封裝成UserDetails對象返回。然后將這些信息封裝到Authentication對象中。
在前后端分離項目中,前端請求的是我們自己定義的認證接口。因為在認證成功以后就需要針對當前用戶生成token,Spring Security中提供的原始認證就無法實現(xiàn)了。在我們自定
義的認證接口中,需要調(diào)用Spring Security的API借助于Spring Security實現(xiàn)認證。
認證:
1、自定義認證接口
① 調(diào)用ProviderManager的方法進行認證 如果認證通過生成jwt
② 把用戶信息存入redis中
2、自定義UserDetailsService
① 在這個實現(xiàn)類中去查詢數(shù)據(jù)庫
校驗:
1、定義Jwt認證過濾器
① 獲取token
② 解析token獲取其中的userid
③ 從redis中獲取用戶信息
④ 存入SecurityContextHolder
<!--redis依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
在application.yml文件中添加Redis的相關(guān)配置
spring:
redis:
host: 127.0.0.1
port: 6379
<!-- 引入mybatis plus的依賴 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3</version>
</dependency>
<!-- 數(shù)據(jù)庫驅(qū)動 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- lombok依賴包 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
CREATE TABLE `sys_user` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
`user_name` VARCHAR(64) NOT NULL DEFAULT 'NULL' COMMENT '用戶名',
`nick_name` VARCHAR(64) NOT NULL DEFAULT 'NULL' COMMENT '昵稱',
`password` VARCHAR(64) NOT NULL DEFAULT 'NULL' COMMENT '密碼',
`status` CHAR(1) DEFAULT '0' COMMENT '賬號狀態(tài)(0正常 1停用)',
`email` VARCHAR(64) DEFAULT NULL COMMENT '郵箱',
`phone_number` VARCHAR(32) DEFAULT NULL COMMENT '手機號',
`sex` CHAR(1) DEFAULT NULL COMMENT '用戶性別(0男,1女,2未知)',
`avatar` VARCHAR(128) DEFAULT NULL COMMENT '頭像',
`user_type` CHAR(1) NOT NULL DEFAULT '1' COMMENT '用戶類型(0管理員,1普通用戶)',
`create_by` BIGINT(20) DEFAULT NULL COMMENT '創(chuàng)建人的用戶id',
`create_time` DATETIME DEFAULT NULL COMMENT '創(chuàng)建時間',
`update_by` BIGINT(20) DEFAULT NULL COMMENT '更新人',
`update_time` DATETIME DEFAULT NULL COMMENT '更新時間',
`del_flag` INT(11) DEFAULT '0' COMMENT '刪除標志(0代表未刪除,1代表已刪除)',
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='用戶表'
-- 插入數(shù)據(jù)
insert into security.sys_user (id, user_name, nick_name, password, status, email, phone_number, sex, avatar, user_type, create_by, create_time, update_by, update_time, del_flag) values (1501123580308578309, 'zhangsan', '張三', '1234', '0', 'hly@itcast.cn', '1312103105', '0', 'http://www.itcast.cn', '1', 1, '2022-03-08 09:12:06', 1, '2022-03-08 09:12:06', 0);
spring:
datasource:
url: jdbc:mysql://localhost:3306/security?characterEncoding=utf-8&serverTimezone=UTC
username: root
password: 1234
driver-class-name: com.mysql.cj.jdbc.Driver
# mybatis plus的配置
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: assign_id
@Data
@TableName(value="sys_user")
public class User {
@TableId
private Long id ; // 唯一標識
private String userName ; // 用戶名
private String nickName ; // 昵稱
private String password ; // 密碼
private String status ; // 狀態(tài) 賬號狀態(tài)(0正常 1停用)
private String email ; // 郵箱
private String phoneNumber ; // 電話號碼
private String sex ; // 性別 用戶性別(0男,1女,2未知)
private String avatar ; // 用戶頭像
private String userType ; // 用戶類型 (0管理員,1普通用戶)
private Long createBy ; // 創(chuàng)建人
private Date createTime ; // 創(chuàng)建時間
private Long updateBy ; // 更新人
private Date updateTime ; // 更新時間
private Integer delFlag ; // 是否刪除 (0代表未刪除,1代表已刪除)
}
public interface UserMapper extends BaseMapper<User> { }
@SpringBootApplication
@MapperScan(basePackages="com.itheima.security.mapper")
public class SecurityApplication {
public static void main(String[] args) {
SpringApplication.run(SecurityApplication.class , args) ;
}
}
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
@SpringBootTest(classes=SecurityApplication.class)
public class SecurityApplicationTest {
@Autowired
private UserMapper userMapper ;
@Test
public void findAll() {
List<User> selectList=userMapper.selectList(new LambdaQueryWrapper<User>());
selectList.forEach( s -> System.out.println(s) );
}
}
在Spring Security的整個認證流程中會調(diào)用會調(diào)用UserDetailsService中的loadUserByUsername方法根據(jù)用戶名稱查詢用戶數(shù)據(jù)。默認情況下調(diào)用的是
InMemoryUserDetailsManager中的方法,該UserDetailsService是從內(nèi)存中獲取用戶的數(shù)據(jù)。現(xiàn)在我們需要從數(shù)據(jù)庫中獲取用戶的數(shù)據(jù),那么此時就需要自定義一個
UserDetailsService來覆蓋默認的配置。
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserMapper userMapper ;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 根據(jù)用戶名查詢用戶數(shù)據(jù)
LambdaQueryWrapper<User> lambdaQueryWrapper=Wrappers.<User>lambdaQuery().eq(User::getUserName ,username) ;
User user=userMapper.selectOne(lambdaQueryWrapper);
// 如果查詢不到數(shù)據(jù),說明用戶名或者密碼錯誤,直接拋出異常
if(user==null) {
throw new RuntimeException("用戶名或者密碼錯誤") ;
}
// 將查詢到的對象轉(zhuǎn)換成Spring Security所需要的UserDetails對象
return new LoginUser(user);
}
}
package com.itheima.security.domain;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
// 用來封裝數(shù)據(jù)庫查詢出來的用戶數(shù)據(jù)
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginUser implements UserDetails {
private User user ;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.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;
}
}
先通過Spring Security提供的默認登錄接口進行認證的測試,需要啟動Redis。此時控制臺會輸出如下錯誤:
報錯的原因:默認情況下Spring Security在獲取到UserDetailsService返回的用戶信息以后,會調(diào)用PasswordEncoder中的matches方法進行校驗,但是此時在Spring容器中并不
存在任何的PasswordEncoder的對象,因此無法完成校驗操作。
解決方案:
① 使用明文認證
要使用明文進行認證,就需要在密碼字段值的前面添加{noop}字樣!
② 配置加密算法
一般情況下關(guān)于密碼在數(shù)據(jù)庫中都是密文存儲的,在進行認證的時候都是基于密文進行校驗。具體的實現(xiàn)步驟:
1、使用指定的加密算法【BCrypt】對密碼進行加密處理,將加密以后的密文存儲到數(shù)據(jù)庫中
2、在Spring容器中注入一個PasswordEncoder對象,一般情況下注入的就是:BCryptPasswordEncoder
我們可以定義一個Spring Security的配置類,Spring Security要求這個配置類要繼承WebSecurityConfigurerAdapter。
@Configuration
public class SpringSecurityConfigurer extends WebSecurityConfigurerAdapter {
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder() ;
}
}
測試:將數(shù)據(jù)庫的用戶密碼更改為使用BCryptPasswordEncoder加密以后的密文
@SpringBootTest(classes=SecurityApplication.class)
public class SecurityApplicationTest {
@Autowired
private PasswordEncoder passwordEncoder ;
@Test
public void testBcrypt() {
// 加密測試
String encode=passwordEncoder.encode("1234");
System.out.println(encode);
// 校驗測試
boolean matches=passwordEncoder.matches("1234", "$2a$10$ZqVB18PPA3P/MR9So/i8N.1UvVb.PblNl2sbj6pQJNDCgqiZqNQUm");
System.out.println(matches);
}
}
整體實現(xiàn)思路:
① 接下我們需要自定義登陸接口,然后讓Spring Security對這個接口放行,讓用戶訪問這個接口的時候不用登錄也能訪問。
② 在接口中我們通過AuthenticationManager的authenticate方法來進行用戶認證,所以需要在Security Config中配置把AuthenticationManager注入容器。
③ 認證成功的話要生成一個jwt,將jwt令牌進行返回。并且為了讓用戶下回請求時能通過jwt識別出具體的是哪個用戶,在返回之前,我們需要把用戶信息存入redis,可以把用戶id
作為key。
在SpringSecurityConfigurer中重寫configure(HttpSecurity http)方法:
// 配置Spring Security的攔截規(guī)則
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable() // 關(guān)閉csrf
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 指定session的創(chuàng)建策略,不使用session
.and() // 再次獲取到HttpSecurity對象
.authorizeRequests() // 進行認證請求的配置
.antMatchers("/user/login").anonymous() // 對于登錄接口,允許匿名訪問
.anyRequest().authenticated(); // 除了上面的請求以外所有的請求全部需要認證
}
在SpringSecurityConfigurer中重寫authenticationManagerBean方法:
@RestController
@RequestMapping(value="/user")
public class UserController {
@Autowired
private UserService userService ;
@PostMapping(value="/login")
public ResponseResult<Map> login(@RequestBody User user) {
return userService.login(user) ;
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ResponseResult<T> {
private Integer code ;
private String msg ;
private T data ;
}
@Service
public class UserServiceImpl implements UserService {
@Autowired
private AuthenticationManager authenticationManager ;
@Autowired
private RedisTemplate<String , String> redisTemplate ;
@Override
public ResponseResult<Map> login(User user) {
// 創(chuàng)建Authentication對象
UsernamePasswordAuthenticationToken authenticationToken=new UsernamePasswordAuthenticationToken(user.getUserName() , user.getPassword()) ;
// 調(diào)用AuthenticationManager的authenticate方法進行認證
Authentication authentication=authenticationManager.authenticate(authenticationToken);
if(authentication==null) {
throw new RuntimeException("用戶名或密碼錯誤");
}
// 將用戶的數(shù)據(jù)存儲到Redis中
LoginUser loginUser=(LoginUser) authentication.getPrincipal();
String userId=loginUser.getUser().getId().toString();
redisTemplate.boundValueOps("login_user:" + userId).set(JSON.toJSONString(loginUser));
// 生成JWT令牌并進行返回
Map<String , String> params=new HashMap<>() ;
params.put("userId" , userId) ;
String token=JwtUtils.getToken(params);
// 構(gòu)建返回數(shù)據(jù)
Map<String , String> result=new HashMap<>();
result.put("token" , token) ;
return new ResponseResult<Map>(200 , "操作成功" , result);
}
}
當用戶在訪問我們受保護的資源的時候,就需要校驗用戶是否已經(jīng)登錄。我們需要自定義一個過濾器進行實現(xiàn)。
過濾器內(nèi)部的邏輯:
1、獲取請求頭中的token,對token進行解析
2、取出其中的userid
3、使用userid去redis中獲取對應的LoginUser對象。
4、然后封裝Authentication對象存入SecurityContextHolder
5、放行
注意:這個過濾器需要將其加入到Spring Security的過濾器鏈中
認證過濾器:
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private RedisTemplate<String , String> redisTemplate ;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 1、從請求頭中獲取token,如果請求頭中不存在token,直接放行即可!由Spring Security的過濾器進行校驗!
String token=request.getHeader("token");
if(token==null || "".equals(token)) {
filterChain.doFilter(request , response);
return ;
}
// 2、對token進行解析,取出其中的userId
String userId=null ;
try {
Claims claims=JwtUtils.getClaims(token);
userId=claims.get("userId").toString();
}catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("token非法") ;
}
// 3、使用userId從redis中查詢對應的LoginUser對象
String loginUserJson=redisTemplate.boundValueOps("login_user:" + userId).get();
LoginUser loginUser=JSON.parseObject(loginUserJson, LoginUser.class);
if(loginUser !=null) {
// 4、然后將查詢到的LoginUser對象的相關(guān)信息封裝到UsernamePasswordAuthenticationToken對象中,然后將該對象存儲到Security的上下文對象中
UsernamePasswordAuthenticationToken authenticationToken=new UsernamePasswordAuthenticationToken(loginUser, null , null) ;
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
// 5、放行
filterChain.doFilter(request , response);
}
}
配置過濾器:
我們只需要定義一個退出接口,然后獲取SecurityContextHolder中的認證信息,刪除redis中對應的數(shù)據(jù)即可。
UserService添加退出登錄接口:
@Override
public ResponseResult logout() {
// 獲取登錄的用戶信息
LoginUser loginUser=(LoginUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
Long userId=loginUser.getUser().getId();
// 刪除Redis中的用戶數(shù)據(jù)
redisTemplate.delete("login_user:" + userId) ;
// 返回
return new ResponseResult(200 , "退出成功" , null) ;
}
權(quán)限系統(tǒng)作用:保證系統(tǒng)的安全性
舉例:例如一個學校圖書館的管理系統(tǒng),如果是普通學生登錄以后使用借書和還書的功能,不可能讓他具有添加書籍信息,刪除書籍信息等功能。但是如果是一個圖書館管理員的賬
號登錄了,應該就能看到并使用添加書籍信息,刪除書籍信息等功能。總結(jié)起來就是不同的用戶可以使用不同的功能,這就是權(quán)限系統(tǒng)要去實現(xiàn)的效果。
權(quán)限功能的實現(xiàn)我們不能只依賴前端去根據(jù)用戶的權(quán)限來選擇顯示哪些菜單、哪些按鈕。因為如果有人知道了對應功能的接口地址就可以不通過前端,直接去發(fā)送請求來實現(xiàn)相關(guān)功
能操作。所以我們還需要在后臺進行用戶權(quán)限的判斷,判斷當前用戶是否有相應的權(quán)限,必須具有所需權(quán)限才能進行相應的操作。
在Spring Security中,會使用默認的FilterSecurityInterceptor來進行權(quán)限校驗。在FilterSecurityInterceptor中會從SecurityContextHolder獲取其中的Authentication,然后
獲取其中的權(quán)限信息。當前用戶是否擁有訪問當前資源所需的權(quán)限。所以我們在項目中只需要把當前登錄用戶的權(quán)限信息也存入Authentication。然后設置我們的資源所需要的權(quán)限
即可。
Spring Security為我們提供了基于注解的權(quán)限控制方案,這也是我們項目中主要采用的方式。我們可以使用注解去指定訪問對應的資源所需的權(quán)限。但是要使用它我們需要先開啟
相關(guān)配置。
在啟動類上添加@EnableGlobalMethodSecurity(prePostEnabled=true)
不給用戶添加任何權(quán)限信息進行測試,返回信息為:
{
"timestamp": "2022-07-04T06:31:47.821+00:00",
"status": 403,
"error": "Forbidden",
"path": "/hello"
}
在UserDetailsServiceImpl中構(gòu)建測試的權(quán)限數(shù)據(jù),并將其設置給LoginUser對象:
LoginUser接收權(quán)限數(shù)據(jù),并且對getAuthorities方法進行改造,返回Spring Security所需要的權(quán)限對象:
在JWT過濾器中需要從Redis中獲取LoginUser對象,在構(gòu)建UsernamePasswordAuthenticationToken對象的時候,為其設置權(quán)限數(shù)據(jù):
RBAC權(quán)限模型(Role-Based Access Control)即:基于角色的權(quán)限控制。這是目前最常被開發(fā)者使用也是相對易用、通用權(quán)限模型。
權(quán)限表(菜單表):
CREATE TABLE `sys_menu` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`menu_name` varchar(64) NOT NULL DEFAULT 'NULL' COMMENT '菜單名',
`path` varchar(200) DEFAULT NULL COMMENT '路由地址',
`component` varchar(255) DEFAULT NULL COMMENT '組件路徑',
`visible` char(1) DEFAULT '0' COMMENT '菜單狀態(tài)(0顯示 1隱藏)',
`status` char(1) DEFAULT '0' COMMENT '菜單狀態(tài)(0正常 1停用)',
`perms` varchar(100) DEFAULT NULL COMMENT '權(quán)限標識',
`icon` varchar(100) DEFAULT '#' COMMENT '菜單圖標',
`create_by` bigint(20) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`update_by` bigint(20) DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
`del_flag` int(11) DEFAULT '0' COMMENT '是否刪除(0未刪除 1已刪除)',
`remark` varchar(500) DEFAULT NULL COMMENT '備注',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='菜單表';
# 插入基礎數(shù)據(jù)
insert into security.sys_menu (id, menu_name, path, component, visible, status, perms, icon, create_by, create_time, update_by, update_time, del_flag, remark) values (1543917775762886657, '添加用戶', '/user/addUser', 'addUser', '0', '0', 'system:user:add', 'icon-add', 1, '2022-07-04 11:20:57', 1, '2022-07-04 11:20:57', 0, '添加用戶按鈕');
insert into security.sys_menu (id, menu_name, path, component, visible, status, perms, icon, create_by, create_time, update_by, update_time, del_flag, remark) values (1543918065589379073, '查看用戶列表', '/user/userList', 'userList', '0', '0', 'system:user:list', 'icon-list', 1, '2022-07-04 11:22:06', 1, '2022-07-04 11:22:06', 0, '查看用戶列表用戶按鈕');
角色表:
CREATE TABLE `sys_role` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(128) DEFAULT NULL,
`role_key` varchar(100) DEFAULT NULL COMMENT '角色權(quán)限字符串',
`status` char(1) DEFAULT '0' COMMENT '角色狀態(tài)(0正常 1停用)',
`del_flag` int(1) DEFAULT '0' COMMENT 'del_flag',
`create_by` bigint(200) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`update_by` bigint(200) DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
`remark` varchar(500) DEFAULT NULL COMMENT '備注',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COMMENT='角色表';
# 插入測試數(shù)據(jù)
insert into security.sys_role (id, name, role_key, status, del_flag, create_by, create_time, update_by, update_time, remark) values (1, '系統(tǒng)管理員', 'admin', '0', 0, 1, '2022-07-04 19:25:06', 1, '2022-07-04 19:25:19', '系統(tǒng)管理員');
insert into security.sys_role (id, name, role_key, status, del_flag, create_by, create_time, update_by, update_time, remark) values (2, '普通用戶', 'user', '0', 0, 1, '2022-07-04 19:25:48', 1, '2022-07-04 19:25:52', '普通用戶角色');
角色菜單中間表:
CREATE TABLE `sys_role_menu` (
`role_id` bigint(200) NOT NULL AUTO_INCREMENT COMMENT '角色ID',
`menu_id` bigint(200) NOT NULL DEFAULT '0' COMMENT '菜單id',
PRIMARY KEY (`role_id`,`menu_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;
# 插入基礎測試數(shù)據(jù)
insert into security.sys_role_menu (role_id, menu_id) values (1, 1543917775762886657);
insert into security.sys_role_menu (role_id, menu_id) values (1, 1543918065589379073);
insert into security.sys_role_menu (role_id, menu_id) values (2, 1543918065589379073);
用戶表:
CREATE TABLE `sys_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
`user_name` varchar(64) NOT NULL DEFAULT 'NULL' COMMENT '用戶名',
`nick_name` varchar(64) NOT NULL DEFAULT 'NULL' COMMENT '昵稱',
`password` varchar(64) NOT NULL DEFAULT 'NULL' COMMENT '密碼',
`status` char(1) DEFAULT '0' COMMENT '賬號狀態(tài)(0正常 1停用)',
`email` varchar(64) DEFAULT NULL COMMENT '郵箱',
`phone_number` varchar(32) DEFAULT NULL COMMENT '手機號',
`sex` char(1) DEFAULT NULL COMMENT '用戶性別(0男,1女,2未知)',
`avatar` varchar(128) DEFAULT NULL COMMENT '頭像',
`user_type` char(1) NOT NULL DEFAULT '1' COMMENT '用戶類型(0管理員,1普通用戶)',
`create_by` bigint(20) DEFAULT NULL COMMENT '創(chuàng)建人的用戶id',
`create_time` datetime DEFAULT NULL COMMENT '創(chuàng)建時間',
`update_by` bigint(20) DEFAULT NULL COMMENT '更新人',
`update_time` datetime DEFAULT NULL COMMENT '更新時間',
`del_flag` int(11) DEFAULT '0' COMMENT '刪除標志(0代表未刪除,1代表已刪除)',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COMMENT='用戶表';
# 插入測試數(shù)據(jù)
insert into security.sys_user (id, user_name, nick_name, password, status, email, phone_number, sex, avatar, user_type, create_by, create_time, update_by, update_time, del_flag) values (1501123580308578309, 'zhangsan', '張三', '$2a$10$ZqVB18PPA3P/MR9So/i8N.1UvVb.PblNl2sbj6pQJNDCgqiZqNQUm', '0', 'hly@itcast.cn', '1312103105', '0', 'http://www.itcast.cn', '1', 1, '2022-03-08 09:12:06', 1, '2022-03-08 09:12:06', 0);
insert into security.sys_user (id, user_name, nick_name, password, status, email, phone_number, sex, avatar, user_type, create_by, create_time, update_by, update_time, del_flag) values (1501123580308578310, 'admin', '系統(tǒng)管理員', '$2a$10$ZqVB18PPA3P/MR9So/i8N.1UvVb.PblNl2sbj6pQJNDCgqiZqNQUm', '0', 'hly@itcast.cn', '1312103105', '0', 'http://www.itcast.cn', '1', 1, '2022-03-08 09:12:06', 1, '2022-03-08 09:12:06', 0);
用戶角色中間表:
CREATE TABLE `sys_user_role` (
`user_id` bigint(200) NOT NULL AUTO_INCREMENT COMMENT '用戶id',
`role_id` bigint(200) NOT NULL DEFAULT '0' COMMENT '角色id',
PRIMARY KEY (`user_id`,`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
# 插入基礎數(shù)據(jù)
insert into security.sys_user_role (user_id, role_id) values (1501123580308578309, 2);
insert into security.sys_user_role (user_id, role_id) values (1501123580308578310, 1);
SQL測試查詢某一個用戶所具有的權(quán)限:
SELECT distinct m.perms FROM sys_user u
left join sys_user_role ur on ur.user_id=u.id
left join sys_role_menu rm on rm.role_id=ur.role_id
left join sys_menu m on m.id=rm.menu_id
WHERE u.id=1501123580308578310 ;
// 菜單表(Menu)實體類
@TableName(value="sys_menu")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Menu {
@TableId
private Long id;
private String menuName; // 菜單名
private String path; // 路由地址
private String component; // 組件路徑
private String visible; // 菜單狀態(tài)(0顯示 1隱藏)
private String status; // 菜單狀態(tài)(0正常 1停用)
private String perms; // 權(quán)限標識
private String icon; // 菜單圖標
private Long createBy; // 創(chuàng)建人
private Date createTime; // 創(chuàng)建時間
private Long updateBy; // 更新人
private Date updateTime; // 更新時間
private Integer delFlag; // 是否刪除(0未刪除 1已刪除)
private String remark; // 備注
}
// 操作菜單表的Mapper接口
public interface MenuMapper extends BaseMapper<Menu> {
// 查詢某一個用戶的權(quán)限信息
public abstract List<String> findUserMenuById(Long userId) ;
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.itheima.security.mapper.MenuMapper">
<select id="findUserMenuById" resultType="java.lang.String">
SELECT distinct m.perms FROM sys_user u
left join sys_user_role ur on ur.user_id=u.id
left join sys_role_menu rm on rm.role_id=ur.role_id
left join sys_menu m on m.id=rm.menu_id
WHERE u.id=#{userId} ;
</select>
</mapper>
從數(shù)據(jù)庫中查詢該用戶的真實權(quán)限信息:
我們還希望在認證失敗或者是授權(quán)失敗的情況下也能和我們的接口一樣返回相同結(jié)構(gòu)的json,這樣可以讓前端能對響應進行統(tǒng)一的處理。要實現(xiàn)這個功能我們需要知道
SpringSecurity的異常處理機制。
在SpringSecurity中,如果我們在認證或者授權(quán)的過程中出現(xiàn)了異常會被ExceptionTranslationFilter捕獲到。在ExceptionTranslationFilter中會去判斷是認證失敗還是授權(quán)失敗出
現(xiàn)的異常。
① 如果是認證過程中出現(xiàn)的異常會被封裝成AuthenticationException然后調(diào)用AuthenticationEntryPoint對象的方法去進行異常處理。
② 如果是授權(quán)過程中出現(xiàn)的異常會被封裝成AccessDeniedException然后調(diào)用AccessDeniedHandler對象的方法去進行異常處理。
所以如果我們需要自定義異常處理,我們只需要自定義AuthenticationEntryPoint和AccessDeniedHandler然后配置給Spring Security即可。
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
ResponseResult result=new ResponseResult(HttpStatus.UNAUTHORIZED.value(), "認證失敗請重新登錄", null);
String json=JSON.toJSONString(result) ;
WebUtils.renderString(response,json);
}
}
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
ResponseResult result=new ResponseResult(HttpStatus.FORBIDDEN.value(), "權(quán)限不足" , null);
String json=JSON.toJSONString(result);
WebUtils.renderString(response,json);
}
}
實現(xiàn)步驟:
1、先注入對應的處理器
2、使用HttpSecurity對象的方法去配置
瀏覽器出于安全的考慮,使用 XMLHttpRequest對象發(fā)起 HTTP請求時必須遵守同源策略,否則就是跨域的HTTP請求,默認情況下是被禁止的。
同源策略要求源相同才能正常進行通信,所謂的源相同指定是:協(xié)議、域名、端口號都完全一致。
前后端分離項目,前端項目和后端項目一般都不是同源的,所以肯定會存在跨域請求的問題。
所以我們就要處理一下,讓前端能進行跨域請求。
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
// 設置允許跨域的路徑
registry.addMapping("/**")
// 設置允許跨域請求的域名
.allowedOriginPatterns("*")
// 是否允許cookie
.allowCredentials(true)
// 設置允許的請求方式
.allowedMethods("GET", "POST", "DELETE", "PUT")
// 設置允許的header屬性
.allowedHeaders("*")
// 跨域允許時間
.maxAge(3600);
}
}
由于我們的資源都會收到Spring Security的保護,所以想要跨域訪問還要讓Spring Security運行跨域訪問。
//SpringSecurityConfigurer#configure 允許跨域
http.cors();
我們前面都是使用@PreAuthorize注解,然后在在其中使用的是hasAuthority方法進行校驗。Spring Security還為我們提供了其它方法例如:hasAnyAuthority,hasRole,
hasAnyRole等。
hasAnyAuthority方法可以傳入多個權(quán)限,只有用戶有其中任意一個權(quán)限都可以訪問對應資源。
hasRole要求有對應的角色才可以訪問,但是它內(nèi)部會把我們傳入的參數(shù)拼接上 ROLE_ 后再去比較。所以這種情況下要用用戶對應的權(quán)限也要有 ROLE_ 這個前綴才可以。
hasAnyRole 有任意的角色就可以訪問。它內(nèi)部也會把我們傳入的參數(shù)拼接上 ROLE_ 后再去比較。所以這種情況下要用用戶對應的權(quán)限也要有 ROLE_ 這個前綴才可以。
我們也可以在配置類中使用使用配置的方式對資源進行權(quán)限控制。
注意: 如果此時在方法上使用了@PreAuthorize(value="hasAuthority('system:user:add')")指定了權(quán)限信息,那么就需要用于同時擁有兩個權(quán)限才可以進行訪問。
CSRF是指跨站請求偽造(Cross-site request forgery),是web常見的攻擊之一。https://blog.csdn.net/freeking101/article/details/86537087
Spring Security去防止CSRF攻擊的方式就是通過csrf_token。后端會生成一個csrf_token,前端發(fā)起請求的時候需要攜帶這個csrf_token,后端會有過濾器進行校驗,如果沒有攜
帶或者是偽造的就不允許訪問。
我們可以發(fā)現(xiàn)CSRF攻擊依靠的是cookie中所攜帶的認證信息。但是在前后端分離的項目中我們的認證信息其實是token,而token并不是存儲在cookie中,并且需要前端代碼去把
token設置到請求頭中才可以,所以CSRF攻擊也就不用擔心了。
本文章給大家介紹了一下在前后端分離項目中如何使用Spring Security完成認證和授權(quán)的相關(guān)操作,并且介紹一下如何自定義認證和授權(quán)失敗的處理器,以及如何解決跨域的相關(guān)
問題。大家可以參考本文章實際操作一下,相信大家很快就可以掌握Spring Security在前后端分離項目中的使用。
論是“雙十一”還是“6.18”,近幾年各大電商平臺造出了一個又一個購物節(jié),期間還真有不少物買價廉的家電產(chǎn)品,不過還是需要我們具有一雙善于發(fā)現(xiàn)的慧眼,一不小心就可能和優(yōu)惠錯過哦!今天筆者就為大家盤點一下,那些6.18真正值得買的家電產(chǎn)品!
又一電商盛宴!6.18家電產(chǎn)品選購全攻略
1.買一送一 三星空氣凈化器KJ720F-K7586WF
作為三星旗下凈化器的旗艦款產(chǎn)品,三星KJ720F-K7586WF空氣凈化器擁有五重過濾技術(shù),能夠有效過濾空氣中漂浮的毛發(fā)、甲醛和微塵,實現(xiàn)客廳空氣的整體凈化。目前這款產(chǎn)品京東售價9990元,買還送三星凈化器一臺,需要的朋友不妨關(guān)注下。
多重濾網(wǎng) 層層凈化
三星專利的超凈3離子群除菌技術(shù),能夠有效去除空氣中的細菌,讓家人免遭各種細菌的侵害,營造健康潔凈的空氣環(huán)境。這款凈化器還配備PM2.5/PM10/氣味三個傳感器并獨立顯示,肉眼看不到的空氣質(zhì)量用數(shù)值以及指示燈直觀顯示,凈化效果一目了然。
三星空氣凈化器KJ720F-K7586WF京東售價9990元
和普通凈化器不同的是,這款產(chǎn)品內(nèi)部設有靜電發(fā)生裝置,能夠使灰塵顆粒物帶電而更易被集塵濾網(wǎng)規(guī)律性吸附,從而大大提高顆粒物CCM,延長濾網(wǎng)的使用壽命。相較濾網(wǎng)直接吸附,能夠提升約2倍濾網(wǎng)壽命。濾網(wǎng)壽命提醒方面,這款產(chǎn)品通過光折射率判斷濾網(wǎng)的污染程度,并計算剩余壽命,能夠更準確提示濾網(wǎng)的壽命。
2.直降500元 戴森V6無線吸塵器
當我們大掃除完成后,看著煥然一新的房間是不是心情頓時舒暢起來?然而打掃完后看似整潔的房間其實并不干凈,空氣中還有很多肉眼不可見的顆粒污染物,嚴重威脅家人的健康,尤其是過敏體質(zhì)者,更容易受到感染。戴森V6 Fluffy手持無線吸塵器,使用最新的整機HEPA濾網(wǎng),全面清潔的同時杜絕二次污染,為家人的健康保駕護航。6.18期間這款吸塵器直降500元,僅售4190元。
戴森V6 Fluffy手持吸塵器僅售4190元
排出的空氣比你呼吸的空氣更干凈
傳統(tǒng)的吸塵器由于密封不嚴等問題,在工作過程中會排除有害過敏原,對家庭環(huán)境造成二次污染,嚴重威脅家人健康。戴森V6整機配備HEPA過濾系統(tǒng),能吸附超過99.9%的小至PM0.3的微塵,后置馬達配備濾網(wǎng)及密封系統(tǒng),有效防止灰塵逸出,杜絕二次污染,確保空氣潔凈。
排出的空氣比你呼吸的空氣更潔凈
整機HEPA過濾系統(tǒng)
強大吸力,不懼頑固塵垢
普通吸塵器吸力不夠,面對頑固灰塵束手無策?戴森V6配備第六代數(shù)碼馬達,憑借其獨特的2 Tier Redial雙層放射式氣旋技術(shù)以及鎳鈷錳鋰電池科技,15個內(nèi)置氣旋同時運作,每分鐘旋轉(zhuǎn)高達110000次,可提供長達20分鐘的強勁吸力,面對“強敵”,游刃有余。無論是硬質(zhì)地板還是天花板上的灰塵,配備可拆式長管及吸頭都可輕松搞定。
內(nèi)置2 Tier Radial cyclones 雙層放射式氣旋
軟絨滾筒刷頭,輕松應對加大塵屑
軟絨滾筒吸頭,輕松應對較大塵屑
軟絨滾筒吸頭的設計,取代傳統(tǒng)有線吸塵器上的刷毛,能夠覆蓋整個清潔滾筒,可以同時吸除硬質(zhì)地板上的殘屑及微塵,只需一次即可潔凈如新。滾筒內(nèi)直驅(qū)式馬達的設計,可以做到全方位、直達邊緣的清潔,不留衛(wèi)生死角,全面呵護家人健康。
3.客廳必備:酷開50寸智能電視
雖然電視的娛樂地位被手機和平板分割,但一個家最少不了的還是一臺大尺寸的電視。創(chuàng)維酷開K50J 50寸智能WIFI電視,因其不僅具有1080p全高清的50英寸液晶顯示屏,同時還采用了杜比專業(yè)級音效解碼,更通過內(nèi)置的電視派整合了愛奇藝,優(yōu)酷等視頻平臺的視頻資源,保障用戶看片找片沒煩惱。6.18提前預約搶購價,僅售1799元。
酷開K50J智能電視
酷開K50J智能電視
酷開 K50J | |
產(chǎn)品定位 | 全高清電視,LED電視,網(wǎng)絡電視,智能電視 |
屏幕尺寸 | 50英寸 |
分辨率 | 1080P(1920*1080) |
屏幕比例 | 16:9 |
面板類型 | 群創(chuàng)面板 |
背光燈類型 | LED發(fā)光二極管 |
最佳觀看距離 | 4.1-5.0米 |
對比度 | 1200:1 |
音效系統(tǒng) | 環(huán)繞音效 |
輸出功率 | 8W×2 |
揚聲器 | 2個揚聲器 |
音效特點 | 杜比解碼:支持 |
網(wǎng)絡功能 | 有線/WiFi,2.4GHz 單頻 WiFi |
USB媒體播放 | USB支持視頻格式:MVC,RMVB,RM,AVI,VOB,MPG,MKV等,視頻硬解碼:支持H.264、H.265硬解碼 USB支持音頻格式:MP3,WMA等 USB支持圖片格式:JPG,JPEG,BMP,GIF |
HDMI接口 | 2*HDMI |
網(wǎng)絡接口 | 1×網(wǎng)絡接口 |
USB接口 | 2×USB接口 |
其他接口 | 1×音視頻接口(組) |
電源性能 | 220V |
產(chǎn)品功耗 | 100W |
待機功耗 | |
能效等級 | 3級能效 |
CPU | 四核CortexA7 |
GPU | 四核Mali450 |
RAM | 1GB 雙通道 DDR3 |
ROM | 4GB eMMC高速閃存 |
操作系統(tǒng) | 酷開系統(tǒng) |
遙控器 | 支持 |
外觀設計 | 機身:超薄機身21.5mm 邊框:極窄邊框12.7mm |
機身尺寸 | 含底座:1125×262×670mm 不含底座:1125×73×652mm |
機身重量 | 含底座:13.2kg 不含底座:12.8kg |
其它性能 | WiFi熱點,Airplay,Miracast |
包裝清單 | 電視機身 x1 遙控器 x1 快速使用指南 x1 保修卡 x1 |
保修政策 | 全國聯(lián)保,享受三包服務 |
質(zhì)保時間 | 1年 |
質(zhì)保備注 | 整機1年,主要部件2年 |
客服電話 | 9510-5555 |
電話備注 | 周一至周日8:30-22:00 |
詳細內(nèi)容 | 酷開TV官網(wǎng)嚴格按照國家三包政策,針對所售商品履行保修、換貨和退貨的義務,產(chǎn)品質(zhì)量問題退換貨發(fā)生的來回運費由酷開TV官網(wǎng)承擔,非質(zhì)量問題退換貨發(fā)生的來回運費均由用戶承擔。請客戶妥善保管好保修卡和購機發(fā)票,這些將是您的保修憑證。用戶可與酷開TV官網(wǎng)的客服中心聯(lián)系辦理退換貨事宜(撥打酷開TV官網(wǎng)客戶服務熱線:95105555)。進入官網(wǎng)>> |
酷開K50J智能電視詳細參數(shù)
編輯點評:酷開K50J屏幕采用1920X1080高清分辨率,屏幕尺寸達到了50寸,支持無線wifi鏈接,內(nèi)置ARM A7架構(gòu)四核CPU和Mali-450四核GPU,同時還具有1GB雙通道DDR3內(nèi)存,各種應用和游戲均可流暢運行。該電視還搭載了快速易用的酷開系統(tǒng)。不僅具有人性化的交互界面,同時還具有毫秒級快速響應能力,各種任務之間可以極速切換,帶來流暢的操作體驗。
4.滿1500減200 松下三門冰箱空前低價
這款外型時尚帶有獨立保鮮功能的冰箱——松下NR-C25EP2-S三門冰箱,它擁有獨立的保鮮室,兩檔溫度區(qū)間,分類保存,持久保鮮,相當實用。目前,這款松下冰箱在京東商城僅售2599元,且每滿1500減200,感興趣的朋友可以關(guān)注一下!
松下NR-C25EP2-S三門冰箱
該機通體白色,銀裝素裹,硬朗的機身線條顯得十分剛毅,隱藏式的把手嵌于門上,美觀優(yōu)雅,便捷易用。機器的寬度僅為56.1cm,體積適中,易于安放,不大的機身卻又著245升的超大容量,再多食材也可一次收攬,足夠全家人享用。
中門獨立保鮮室 保鮮持久
內(nèi)置松下原裝壓縮機,性能勁猛,動力強勁,極其省電,國家一級能效認證,同時振動更弱,噪音更小,即便正常運行也幾乎察覺不到它的存在,融入靜謐的家居環(huán)境中。中門特色的獨立保鮮室,具有兩檔溫度區(qū)間可調(diào),不論是蔬菜水果飲料,還是肉類海鮮奶酪,均可輕松放入,持久保鮮,讓您隨時隨地即刻享受可口美味。
細節(jié)設計彰顯大廠風采
諸多的細節(jié)設計彰顯出松下對產(chǎn)品的用心:門自鎖系統(tǒng),開合自如不反彈;氣囊式門封條,吸附力強,保溫效果顯著;鋼化玻璃擱板,美觀耐用;LED冷光源,亮度更高,照明范圍更廣闊。
原裝壓縮機 節(jié)能靜音
編輯點評:松下專注冰箱領(lǐng)域已經(jīng)60余年,有著業(yè)界領(lǐng)先的研發(fā)實力和技術(shù)積累,其生產(chǎn)的壓縮機更是品質(zhì)優(yōu)異,質(zhì)量可靠,松下的這款冰箱亦是擁有眾多優(yōu)勢:簡約時尚的外型,獨立保鮮室的加入,性能強能耗低,省電靜音,還有許多人性化的細節(jié)設計等等,喜歡的朋友不要錯過了!
5.成交價1699 TCL7公斤滾筒洗衣機
一說到洗衣機你腦中浮現(xiàn)出怎樣的畫面?白色的四方盒子又笨又重噪音還挺大,也難怪,受制于技術(shù)的限制,傳統(tǒng)的機器基本都是那副模樣,可今天,筆者為大家?guī)硪豢顒e具一格的新產(chǎn)品——TCL XQG70-F12102THB洗衣機,相信它的出現(xiàn)定會讓你眼前一亮。小巧的整體設計,一襲乳白色的外衣覆蓋表面,硬朗的機身線條剛正沉穩(wěn),正面圓形的大眼睛深邃而靈犀,炯炯有神。它不像是一臺洗衣機,倒仿佛是一個鮮活的小生命,朝氣而又不失優(yōu)雅。
TCL XQG70-F12102THB洗衣機【點此購買】
該機不但擁有出色的機身外觀設計,內(nèi)在的強大性能更是讓人為之贊嘆。搭載的源自德國設計CIM交流變頻電機,能效強勁,動力澎湃,同時振動更弱,噪音更小,節(jié)能減耗,壽命長久。
變頻電機 性能強勁
7公斤的大容量,再多的衣物也可一次洗凈,省心省力。中途加衣的貼心設計徹底解決漏洗煩惱,拯救一天好心情。創(chuàng)新式的采用了內(nèi)凸式蜂巢水晶內(nèi)筒,極大的降低了對衣物的磨損,精心呵護每一件衣服。大面積的顯示操控面板,字跡清晰一目了然,操作便捷,簡單上手。該機內(nèi)置16種洗滌程序,面對多種衣物也從容不迫,讓洗衣真正做到了因地制宜。
內(nèi)凸式水晶內(nèi)筒 呵護衣物
模糊稱重設計的加入,它可以根據(jù)衣物的多少智能選擇合適的電量與水量,精準把控,為您省電更是省錢。1200轉(zhuǎn)的超高轉(zhuǎn)速,甩脫更干凈徹底,減少晾曬時間。90度高溫自潔功能有效清潔內(nèi)筒,保持內(nèi)筒干凈衛(wèi)生,杜絕二次污染,全方位呵護您和家人的身體健康。
眾多人性化的細節(jié)設計
TCL的這款洗衣機是一款內(nèi)外兼修的好產(chǎn)品,它有著出眾的外觀設計,強勁的洗滌效能,創(chuàng)新的內(nèi)筒不傷衣物,大面積的顯示面板便捷易用,眾多實用貼心的功能和設計讓洗衣變得簡單有趣,使用隨心自如。性價比突出,喜歡的朋友不要錯過了哦!
6.科龍KFR-50LW/EFVDN2z柜式空調(diào)
這款科龍KFR-50LW/EFVDN2z柜式空調(diào),有著高挑的機身和圓潤的造型,金色鋼化玻璃滑動面板鑲嵌其中,渾然天成。外觀優(yōu)秀,其性能也十分強勁,冬天電輔制熱,三分鐘即可暖房,讓您提前溫暖愜意;夏天制冷迅速,僅需一分鐘讓您倍感涼爽舒適,無需等待。智能化的遠程控制,無論你在何處,空調(diào)狀態(tài)盡收眼底,運行狀況一目了然,并且可以實時操控,簡單便捷。搭載的高效品質(zhì)壓縮機,性能強勁,動力十足,制溫速,噪音小,節(jié)能減耗。目前,這款空調(diào)在京東商城售價3999元,喜歡的朋友可以關(guān)注一下!
科龍KFR-50LW/EFVDN2z柜式空調(diào)
時尚優(yōu)雅的外觀設計
迅捷的制溫效果
編輯點評:其實空調(diào)的角色,早已從基本的功能性制溫,發(fā)展到居家裝飾的效果,這款產(chǎn)品無疑是一個很好的解決方案,它擁有高貴典雅的外觀設計,強勁的性能制溫迅速,人性化的觸控操作簡單便捷,除甲醛技術(shù)呵護健康等等,它各方面素質(zhì)表現(xiàn)都很優(yōu)秀,目前京東商城售價3999元,喜歡的朋友不要錯過哦!
7.方太HA21BE燃氣灶
俗話說:民以食為天。想要做出美味可口的三餐,一款好爐具必不可少,接下來筆者今天就為大家奉上一款實用、健康的好產(chǎn)品——方太HA21BE燃氣灶。它采用三層防爆玻璃黑色面板,一體化機身,沉穩(wěn)大氣,便于清潔。超大的爐頭,強勁火力,形成有效燃燒區(qū)域,提升熱效率。防漏氣、防熄火、防滲水、防內(nèi)火的四防設計,重重防護,令烹飪無后顧之憂。獨有的精準火力控制能在大火爆炒時鎖住食物維生素不流失,在小火慢燉時充分釋放營養(yǎng)成分。目前,這款燃氣灶在京東商城僅售2780元,值得選購!
方太HA21BE燃氣灶
大氣沉穩(wěn)的外觀設計
直噴技術(shù) 火力強勁
編輯點評:沉穩(wěn)大氣的造型,安全的防護,勁猛的火力,簡單便捷的操控和貼心的細節(jié)設計,都是這款產(chǎn)品的優(yōu)勢,目前京東商城售價2780元,極具性價比,喜歡的美食的朋友不要錯過哦!
8.博瑞客U700家用加濕器
目前市面上加濕器多數(shù)為國產(chǎn)代工產(chǎn)品,某些品牌憑借線下知名度,為了節(jié)省生產(chǎn)研發(fā)成本尋找一些代加工廠貼牌生產(chǎn),只為低價,只為銷量和銷售額而全然不顧產(chǎn)品本質(zhì)的提升,顧客使用體驗是否優(yōu)越。只有高質(zhì)量的產(chǎn)品才能保障家人的健康,而博瑞客U700正是這樣一款產(chǎn)品,為家人健康保駕護航。
博瑞客U700大容量加濕器
超聲波系列加濕器,加濕是通過震蕩片將水打碎然后通過風力系統(tǒng)吹入空氣當中,在將水霧化的過程中水中的礦物質(zhì)也一同被分離并進入空氣當中-簡稱白粉,對人體呼吸系統(tǒng)有產(chǎn)生影響。BONECO品牌設計的超聲波加濕器系列為了解決這一問題,絕大多數(shù)安裝了去礦盒,可以有效去除水中礦物質(zhì),U700 也不例外裝備最新款去礦盒(去礦粒為黑色老款為白色),真正的健康加濕~
博瑞客U700大容量加濕器
這款產(chǎn)品還內(nèi)置加熱功能,可手動打開和關(guān)閉,可以加熱噴出的霧氣,溫度大約在35℃左右,室內(nèi)溫度會在一個十分舒適的程度,加熱到最佳程度僅需幾分鐘時間。
博瑞客U700大容量加濕器
嚴重的空氣污染讓人們對空氣凈化的需求越來越強烈,但單純的凈化并不能帶來持久的健康,由于空氣凈化器在工作中會帶走空氣中的水分,因此會使空氣更加干燥。博瑞客U700大容量加濕器采用智能環(huán)境監(jiān)控技術(shù),支持冷熱雙模式,可自動清洗,是一款相當不錯的產(chǎn)品。
位玩家大家好,歡迎大家收看今天的游民晨播報,我是優(yōu)格。今日要聞有:分析師稱XGP收入將暴漲;制作人稱《MVC》系列或有新作;《2077》制作人稱游戲中仍有大量彩蛋未被發(fā)現(xiàn)。我們將在隨后為你帶來以上新聞的詳細信息,敬請關(guān)注。
重點關(guān)注:
1分析師稱XGP收入將暴漲
Ampere Analysis的研究主管Piers Harding-Rolls近日對XGP的漲價進行了分析,他表示,到2025 年,Game Pass的收入預計將上升到驚人的51億美元。與2022年相比,Microsoft的收入將增長200%以上,而分析師稱,Game Pass Ultimate的價格上漲將成為這一增長的催化劑。
分析師表示,到2025年,消費者在該服務上的支出將增長高達15.3%。預計今年將小幅增長5.4%。
2制作人稱《MVC》系列或有新作
《漫威vs卡普空戰(zhàn)斗合集》已于近日登陸Switch、PS4以及PC平臺,而目前該系列的制作人松木修平在采訪中表示:“我們的開發(fā)團隊有著遠大的夢想。也許未來有機會推出一款新的《漫威vs卡普空》游戲。”
松本修平承認,任何重振該系列的計劃都需要時間去執(zhí)行。不過,工作室目前正集中精力做力所能及的事情。這也是為什么最近公布的系列作品會引起大家關(guān)注的原因。
3《2077》制作人稱游戲中仍有大量彩蛋未被發(fā)現(xiàn)
《賽博朋克2077:往日之影》制作人Pawel Sasko近日在播客中被問及:本體游戲和DLC中是否還有玩家未發(fā)現(xiàn)的彩蛋存在?他表示:我非常確定是有的。
Sasko補充說:“我現(xiàn)在就知道幾個我之前從未發(fā)現(xiàn)過的例子,但玩家需要花費很長的時間去找。我們在《巫師3》中隱藏的一些彩蛋和細節(jié)是在發(fā)售七年后才被發(fā)現(xiàn)的,是在我們推出完整版之后。直到那時,人們才發(fā)現(xiàn)了一些東西。我們新加入了一些東西,但有些東西是‘一直’存在的。”
以上就是今天的晨報內(nèi)容,我們會在每天的早晨,以快訊的形式為大家播報昨日深夜至今日凌晨國內(nèi)外發(fā)生的重要資訊。我是優(yōu)格,祝愿大家身體健康、工作順利,我們明天再見。
*請認真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。