1 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.2</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>com.gzz</groupId>
<artifactId>spring-security-06</artifactId>
<version>1.0</version>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2 简洁的资源鉴权
package com.gzz.config;
import java.util.List;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
/**
* @summary 鉴权-资源验证处理
* @author 高振中
* @date 2023-02-18 22:20:20
**/
@Slf4j
@Component("resourcesCheck")
public class ResourcesCheck {
public boolean check(Authentication authentication, HttpServletRequest request) {
// 如果要解析token就从request中取出就可以了
// 再如果不使用session,参数authentication也没有用了,只需从token反解出userId然后从缓存中取出authentication
// 这时可以在Security主配置文件中关闭session
// 这种方式不用任何注解,就可以对资源(controller中requestMapping的value)也就是请求路径进行最灵活的配置(在数据库中)
Object principal = authentication.getPrincipal();
if (principal instanceof UserDetails) {
SysUser sysUser = (SysUser) principal;
List<String> resources = sysUser.getResources();
log.info("sysUser={},resources={},request.getRequestURI()={}", sysUser, resources, request.getRequestURI());
return resources.contains(request.getRequestURI());
}
return false;
}
}
2023-02-18 21:42:33,027[INFO][FrameworkServlet.java:554]:Completed initialization in 1 ms
2023-02-18 21:42:33,111[INFO][HikariDataSource.java:110]:test - Starting...
2023-02-18 21:42:33,323[INFO][HikariPool.java:565]:test - Added connection com.mysql.cj.jdbc.ConnectionImpl@774f3160
2023-02-18 21:42:33,325[INFO][HikariDataSource.java:123]:test - Start completed.
2023-02-18 21:42:35,833[INFO][ResourcesCheck.java:29]:sysUser=com.gzz.config.SysUser [Username=高振中, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[ROLE_USER]],resources=[/user/add, /user/update],request.getRequestURI()=/error
2023-02-18 21:42:35,852[INFO][ResourcesCheck.java:29]:sysUser=com.gzz.config.SysUser [Username=高振中, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[ROLE_USER]],resources=[/user/add, /user/update],request.getRequestURI()=/error
2023-02-18 21:42:35,866[INFO][ResourcesCheck.java:29]:sysUser=com.gzz.config.SysUser [Username=高振中, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[ROLE_USER]],resources=[/user/add, /user/update],request.getRequestURI()=/favicon.ico
2023-02-18 21:42:38,534[INFO][ResourcesCheck.java:29]:sysUser=com.gzz.config.SysUser [Username=高振中, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[ROLE_USER]],resources=[/user/add, /user/update],request.getRequestURI()=/error
2023-02-18 21:42:38,560[INFO][ResourcesCheck.java:29]:sysUser=com.gzz.config.SysUser [Username=高振中, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[ROLE_USER]],resources=[/user/add, /user/update],request.getRequestURI()=/error
2023-02-18 21:42:38,576[INFO][ResourcesCheck.java:29]:sysUser=com.gzz.config.SysUser [Username=高振中, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[ROLE_USER]],resources=[/user/add, /user/update],request.getRequestURI()=/favicon.ico
2023-02-18 21:43:02,813[INFO][ResourcesCheck.java:29]:sysUser=com.gzz.config.SysUser [Username=高振中, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[ROLE_USER]],resources=[/user/add, /user/update],request.getRequestURI()=/user/add
2023-02-18 21:43:09,627[INFO][ResourcesCheck.java:29]:sysUser=com.gzz.config.SysUser [Username=高振中, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[ROLE_USER]],resources=[/user/add, /user/update],request.getRequestURI()=/user/list
2023-02-18 21:43:22,458[INFO][LogoutSuccess.java:23]:authentication=UsernamePasswordAuthenticationToken [Principal=com.gzz.config.SysUser [Username=高振中, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[ROLE_USER]], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=1E935E5AD193CDB5C1F64515034A0216], Granted Authorities=[ROLE_USER]]
3 建表
CREATE TABLE `user` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
`code` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '编码',
`name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '姓名',
`password` varchar(80) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '密码',
`roles` varchar(80) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '角色集',
`resources` varchar(80) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '资源集',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
INSERT INTO `user` VALUES (1, 'zhang', '张三', '$2a$10$JejfDoeJ7dn/xUPI051JDO1Jr1H/9MnlXqa7GChitOqlcSfjwyxxW', 'ROLE_ADMIN,ROLE_USER', '/user/add,/user/update,/user/list,/user/delete');
INSERT INTO `user` VALUES (2, 'lisi', '李四', '$2a$10$JejfDoeJ7dn/xUPI051JDO1Jr1H/9MnlXqa7GChitOqlcSfjwyxxW', 'ROLE_ADMIN,ROLE_USER', '/user/add,/user/update,/user/list,/user/delete');
INSERT INTO `user` VALUES (3, 'gzz', '高振中', '$2a$10$JejfDoeJ7dn/xUPI051JDO1Jr1H/9MnlXqa7GChitOqlcSfjwyxxW', 'ROLE_USER', '/user/add,/user/update');
INSERT INTO `user` VALUES (4, 'fbb', '范冰冰', '$2a$10$JejfDoeJ7dn/xUPI051JDO1Jr1H/9MnlXqa7GChitOqlcSfjwyxxW', 'ROLE_ADMIN', '/user/list,/user/delete');
1676728473621.png
4 spring-security主配置类
package com.gzz.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.expression.DefaultHttpSecurityExpressionHandler;
import org.springframework.security.web.access.expression.WebExpressionAuthorizationManager;
import com.gzz.config.handler.AccessDenied;
import com.gzz.config.handler.LoginFailure;
import com.gzz.config.handler.LoginSuccess;
import com.gzz.config.handler.LogoutSuccess;
import com.gzz.sys.UserService;
/**
* @summary spring-security主配置类
* @author 高振中
* @date 2023-02-18 22:20:20
**/
@Configuration
public class SecurityConfig {
@Autowired
private LoginSuccess loginSuccess;
@Autowired
private LoginFailure loginFailure;
@Autowired
private AccessDenied accessDenied;
@Autowired
private EntryPoint entryPoint;
@Autowired
private LogoutSuccess logoutSuccess;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity security) throws Exception {
logoutSuccess.setDefaultTargetUrl("/logout_success");
security//
.authorizeHttpRequests(auth -> auth //
.requestMatchers("/index.html", "/fonts/**", "/logout_success").permitAll() // 这些请求(url)无需授权
.anyRequest().access(expressionManager()))
.formLogin((auth) -> auth//
.usernameParameter("username") //
.passwordParameter("password") //
.loginPage("/index.html") //
.loginProcessingUrl("/login") //
.successHandler(loginSuccess) //
.failureHandler(loginFailure) //
.permitAll())
.logout(auth -> auth//
.deleteCookies("JSESSIONID")//
.logoutSuccessHandler(logoutSuccess).clearAuthentication(true))
.exceptionHandling(exp -> exp//
.accessDeniedPage("/index.html")//
.accessDeniedHandler(accessDenied)//
.authenticationEntryPoint(entryPoint))//
.csrf(csrf -> csrf.disable());
return security.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Autowired
private UserService service;
public void configureGlobal(@Autowired AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(service).passwordEncoder(passwordEncoder());
}
@Autowired
private ApplicationContext applicationContext;
@Bean
public WebExpressionAuthorizationManager expressionManager() {
DefaultHttpSecurityExpressionHandler expressionHandler = new DefaultHttpSecurityExpressionHandler();
expressionHandler.setApplicationContext(applicationContext);
WebExpressionAuthorizationManager authorizationManager = new WebExpressionAuthorizationManager("@resourcesCheck.check(authentication,request)");
authorizationManager.setExpressionHandler(expressionHandler);
return authorizationManager;
}
}
5 UserService
package com.gzz.sys;
import java.util.Arrays;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
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 org.springframework.util.CollectionUtils;
import com.gzz.config.SysUser;
/**
* @类说明 spring-security从数据库加载用户相关的资源数据
* @author 高振中
* @date 2023-02-18 22:23:28
**/
@Service
public class UserService implements UserDetailsService {
@Autowired
private UserDao userDao; // 注入【user】数据访问层
// 1.不论数据库如何设计,最终都是要解决【用户】拥有哪些【角色】最终分配了多少【资源】的问题,RBAC不在security的讨论范围
// 2.假定数据已经组装好,最终从数据库里取到的是【用户名,密码,角色集,资源集】这些数据,然后看在security中如何使用
@Override
public UserDetails loadUserByUsername(String code) {
List<User> users = userDao.list(code);
if (CollectionUtils.isEmpty(users))
throw new UsernameNotFoundException("用户不存在");
User user = users.get(0);
List<String> roles = Arrays.asList(user.getRoles().split(","));
List<String> resources = Arrays.asList(user.getResources().split(","));
return new SysUser(user.getName(), user.getPassword(), roles, resources);
}
}
网友评论