赞
踩
新建springBoot工程导入如下依赖
包括了后续需要用到的数据库操作相关的组件
<!--Spring security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--SpringBoot druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<!-- Mysql Connector -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--SpringBoot druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<!--SpringBoot mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!--Spring Web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
增加一个接口
@RestController
@RequestMapping("/user")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@GetMapping("/{id}")
public R<Object> queryById(@PathVariable Long id){
new FilterChainProxy();
return R.ok( userService.queryById(id));
}
}
增加配置文件和数据库表结构
spring:
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/zcct-user?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: zclvct
# 初始连接数
initial-size: 5
# 最小连接数
min-idle: 10
# 最大连接数
max-active: 20
# 获取连接超时时间
max-wait: 5000
# 连接有效性检测时间
time-between-eviction-runs-millis: 60000
# 连接在池中最小生存的时间
min-evictable-idle-time-millis: 300000
# 连接在池中最大生存的时间
max-evictable-idle-time-millis: 900000
test-while-idle: true
test-on-borrow: false
test-on-return: false
# 检测连接是否有效
validation-query: select 1
# 配置监控统计
webStatFilter:
enabled: true
stat-view-servlet:
enabled: true
url-pattern: /druid/*
reset-enable: false
filter:
stat:
enabled: true
# 记录慢SQL
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: true
wall:
config:
multi-statement-allow: true
security:
user:
name: admin
password: admin
server:
port: 9001
CREATE TABLE `sys_user` (
`user_id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`dept_id` bigint DEFAULT NULL COMMENT '部门ID',
`tenant_id` bigint DEFAULT NULL COMMENT '租户ID',
`user_name` varchar(30) NOT NULL COMMENT '用户账号',
`nick_name` varchar(30) NOT NULL COMMENT '用户昵称',
`user_type` tinyint DEFAULT '0' COMMENT '用户类型(0管理员用户, 01系统用户 )',
`email` varchar(50) DEFAULT '' COMMENT '用户邮箱',
`phone` varchar(11) DEFAULT '' COMMENT '手机号码',
`sex` tinyint DEFAULT '0' COMMENT '用户性别(0男 1女 2未知)',
`avatar` varchar(100) DEFAULT '' COMMENT '头像地址',
`password` varchar(100) DEFAULT '' COMMENT '密码',
`status` tinyint DEFAULT '0' COMMENT '帐号状态(0正常 1停用)',
`login_ip` varchar(128) DEFAULT '' COMMENT '最后登录IP',
`login_date` datetime DEFAULT NULL COMMENT '最后登录时间',
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`is_deleted` tinyint DEFAULT '0' COMMENT '删除标志',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=100 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户信息表';
启动springBoot功能 访问 /user/1 接口
可以看到引入了 Spring Security 在访问接口是已经把接口保护了起来
输入我们在配置文件中配置的账户和密码
点击登陆后能正常访问到接口并返回数据了
上面提到的快速入门操作就是 Spring Security 提供的 HttpBasic登录验证模式 ,他是最简单的登录认证模式
他的执行流程基本如下图所示
因为 spring boot 集成 Spring Security,通过 spring boot 的自动装配,其实在启动项目时做了很多操作
JWT是 Json Web Token 的缩写。它是基于 RFC 7519 标准定义的一种可以安全传输的 小巧 和 自包含 的JSON对象。由于数据是使用数字签名的,所以是可信任的和安全的。JWT可以使用HMAC算法对secret进行加密或者使用RSA的公钥私钥对来进行签名。
利用 Spring Security 过滤器链,根据前端传递的的token信息
整理执行流程如下流程如下:
### 增加 WebSecurityConfig 配置类
```java
private final ApplicationContext applicationContext;
private final JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Bean
public PasswordEncoder passwordEncoder() {
// 密码加密方式
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 关闭 csrf
.csrf().disable()
.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
// 不通过session获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
// 防止iframe 造成跨域
.and()
.headers()
.frameOptions()
.disable()
.and()
.authorizeRequests()
// login 允许匿名访问
.antMatchers("/auth/login").permitAll();
}
在 jwtAuthenticationTokenFilter 实现对请求的token进行解析和验证 ,jwtAuthenticationTokenFilter 应放在 UsernamePasswordAuthenticationFilter 过滤器之前,在验证账户名和密码前设置 UsernamePasswordAuthenticationToken
@Slf4j
@Component
@RequiredArgsConstructor
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
private final JwtTokenProvider jwtTokenProvider;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String token = request.getHeader("Authorization");
if(StrUtil.isNotBlank(token)) {
try{
jwtTokenProvider.parseJwt(token);
}catch (Exception e) {
e.printStackTrace();
log.debug("非法Token:{}", token);
}
}
filterChain.doFilter(request,response);
}
}
@Slf4j
@Component
public class JwtTokenProvider {
@Resource
private RedisUtil redisUtil;
public static final String AUTHORITIES_KEY = "user";
private JwtParser jwtParser;
private JwtBuilder jwtBuilder;
private static String BASE64_SECRET = "ZmQ0ZGI5NjQ0MDQwY2I4MjMxY2Y3ZmI3MjdhN2ZmMjNhODViOTg1ZGE0NTBjMGM4NDA5NzYxMjdjOWMwYWRmZTBlZjlhNGY3ZTg4Y2U3YTE1ODVkZDU5Y2Y3OGYwZWE1NzUzNWQ2YjFjZDc0NGMxZWU2MmQ3MjY1NzJmNTE0MzI=";
/**
* 初始化
*/
@PostConstruct
public void init() {
Key key = generalKey();
jwtParser = Jwts.parserBuilder()
.setSigningKey(key)
.build();
jwtBuilder = Jwts.builder()
.signWith(key, SignatureAlgorithm.HS512);
}
/**
* 由字符串生成加密key
* @return
*/
public static Key generalKey() {
byte[] encodedKey = Base64.decodeBase64(BASE64_SECRET);
Key key = Keys.hmacShaKeyFor(encodedKey);
return key;
}
/**
* 创建Token
* @param userId
* @return
*/
public String createToken(String userId, JwtUser jwtUser) {
String token = jwtBuilder
// 加入ID确保生成的 Token 都不一致
.setId(IdUtil.simpleUUID())
.claim(AUTHORITIES_KEY, userId)
.setSubject(userId)
.compact();
redisUtil.set("login:"+userId,jwtUser,1L, TimeUnit.HOURS);
return token;
}
/**
* 解析 jtw 保存
* @param token
*/
public void parseJwt(String token) {
if (StringUtils.hasText(token) && token.startsWith("Bearer ")) {
// 去掉令牌前缀
token = token.replace("Bearer ", "");
} else {
log.debug("非法Token:{}", token);
}
Claims claims = jwtParser.parseClaimsJws(token).getBody();
String userId = claims.getSubject();
JwtUser jwtUser = (JwtUser)redisUtil.get("login:" + userId);
if(ObjectUtil.isEmpty(jwtUser)) {
throw new RuntimeException("请求未认证");
}
Object authoritiesStr = claims.get(AUTHORITIES_KEY);
Collection<? extends GrantedAuthority> authorities =
ObjectUtil.isNotEmpty(authoritiesStr) ?
Arrays.stream(authoritiesStr.toString().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList()) : Collections.emptyList();
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(jwtUser,"",authorities);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}
用户登录流程图
public Map<String, Object> login(LoginUser loginUser) {
String username = loginUser.getUsername();
String password = loginUser.getPassword();
// 生成一个 AuthenticationToken
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username,password);
// 使用 authenticationManager 进行认证
Authentication authentication = authenticationManagerBuilder.getObject().authenticate(token);
JwtUser jwtUser = (JwtUser)authentication.getPrincipal();
Long userId = jwtUser.getUser().getUserId();
String jwtToken = jwtTokenProvider.createToken(String.valueOf(userId),jwtUser);
Map<String, Object> authInfo = new HashMap<String, Object>(2) {{
put("token", "Brear " + jwtToken);
put("user", userId);
}};
return authInfo;
}
在调用 authenticationManagerBuilder.getObject().authenticate(token); 时, 会使用UserDetailsService加载用户信息进行验证,在这里需要增加自己的查询用户信息逻辑,并生成返回前端的token
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userService.findByUserName(username);
if (user == null) {
throw new BadRequestException("账号不存在");
}
if(user.getStatus() == 1) {
throw new BadRequestException("账号已停用");
}
List<GrantedAuthority> authorities = roleService.getGrantedAuthorities(user);
Set<String> roleKeys = roleService.getRoleKeys(user);
return new JwtUser(user,roleKeys,authorities);
}
创建相关注解
编写通知
@Aspect
@Component
public class AuthorizeAspect {
/**
* 配置切入点
*/
@Pointcut("@annotation(com.zcct.security.demo.security.annotation.RequireRules) " +
"|| @annotation(com.zcct.security.demo.security.annotation.RequiresLogin) " +
"|| @annotation(com.zcct.security.demo.security.annotation.RequiresPermissions) " +
"|| @annotation(com.zcct.security.demo.security.annotation.RequireAnonymous)")
public void pointcut() {
// 该方法无方法体,主要为了让同类中其他方法使用此切入点
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
authorize(method);
Object obj = joinPoint.proceed();
return obj;
}
private void authorize(Method method) {
RequireRules requireRules = method.getAnnotation(RequireRules.class);
if(ObjectUtil.isNotEmpty(requireRules)) {
SecurityUtils.checkRules(requireRules.value());
}
RequiresLogin requiresLogin = method.getAnnotation(RequiresLogin.class);
if(ObjectUtil.isNotEmpty(requiresLogin)) {
SecurityUtils.checkLogin();
}
RequiresPermissions requiresPermissions = method.getAnnotation(RequiresPermissions.class);
if(ObjectUtil.isNotEmpty(requiresPermissions)) {
SecurityUtils.checkPermissions(requiresPermissions.value());
}
RequireAnonymous requireAnonymous = method.getAnnotation(RequireAnonymous.class);
if(ObjectUtil.isNotEmpty(requireAnonymous)) {
SecurityUtils.checkAnonymous();
}
}
}
增加工具类
public class SecurityUtils {
/**
* 获取当前登录用户
* 建议 保存在
* @return
*/
public static JwtUser getCurrentUser() {
UserDetailsService userDetailsService = SpringUtil.getBean(UserDetailsService.class);
return (JwtUser)userDetailsService.loadUserByUsername(getCurrentUsername());
}
/**
* 获取当前登录用户名
* @return
*/
public static String getCurrentUsername() {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
throw new BadRequestException(HttpStatus.FORBIDDEN,"当前登录状态过期");
}
if (authentication.getPrincipal() instanceof UserDetails) {
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
return userDetails.getUsername();
}
throw new BadRequestException(HttpStatus.FORBIDDEN,"找不到当前登录的信息");
}
/**
* 检查角色权限
* @param rules
*/
public static void checkRules(String[] rules) {
Set<String> ruleKeys = SecurityUtils.getCurrentUser().getRuleKeys();
boolean hasAuthority = ruleKeys.contains("admin") || Arrays.stream(rules).anyMatch(ruleKeys::contains);
if(!hasAuthority) {
throw new BadRequestException(HttpStatus.UNAUTHORIZED,"没有权限");
}
}
/**
* 检查是否登录
*/
public static void checkLogin() {
getCurrentUsername();
}
/**
* 检查权限
* @param permissions
*/
public static void checkPermissions(String[] permissions) {
// 获取所有权限
List<String> allPermissions = SecurityUtils.getCurrentUser().getAuthorities().
stream().map(GrantedAuthority::getAuthority).
collect(Collectors.toList());
boolean hasAuthority = allPermissions.contains("admin") || Arrays.stream(permissions).anyMatch(allPermissions::contains);
if(!hasAuthority) {
throw new BadRequestException(HttpStatus.UNAUTHORIZED,"没有权限");
}
}
public static void checkAnonymous() {
UserDetails userDetails = null;
try{
userDetails = getCurrentUser();
}catch (Exception e){
}
if(ObjectUtil.isNotEmpty(userDetails)) {
throw new BadRequestException(HttpStatus.UNAUTHORIZED,"不允许访问");
}
}
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。