SpringSecurity系列(一) 初识 Spring Security
SpringSecurity系列(二) Spring Security入门
1. 服务端
1.1 依赖
<?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 https://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>2.3.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.javaboy</groupId>
<artifactId>vms</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>vms</name>
<description>vms project for Spring Boot</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
<version>5.1.27</version>
</dependency>
<!-- druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<!--pageHelper-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.10</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.74</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- maven插件 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
1.2 配置文件
server:
port: 8090
servlet:
context-path: /vms
address:
spring:
profiles:
active: dev
application:
name: vms
servlet:
multipart:
maxFileSize: 100MB
maxRequestSize: 100MB
# 数据源配置
datasource:
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://127.0.0.1:3306/vms?useUnicode=true&characterEncoding=utf-8&useSSL=false&autoReconnect=true&serverTimezone=UTC
data-username: root
data-password: root
druid:
# 初始化时建立物理连接的个数,
initial-size: 5
# 最小连接池数量
min-idle: 5
# 最大连接池数量
max-active: 20
# 获取连接时最大等待时间,单位毫秒
max-wait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位毫秒
time-between-eviction-runs-millis: 60000
# 配置一个连接在池中最小生存的时间,单位毫秒
min-evictable-idle-time-millis: 300000
validation-query: SELECT 1 FROM DUAL
# 申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
test-while-idle: true
# 申请连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
test-on-borrow: false
# 归还连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
test-on-return: false
# 是否缓存preparedStatement,也就是PSCache,PSCache对支持游标的数据库性能提升巨大,比如说oracle,在mysql下建议关闭。mysql5.5+建议开启
pool-prepared-statements: true
# 当值大于0时poolPreparedStatements会自动修改为true
max-pool-prepared-statement-per-connection-size: 20
# 通过别名的方式配置扩展插件: stat:监控统计,wall:防sql注入,log4j:日志
filters: stat,wall,slf4j
# 合并多个DruidDataSource的监控数据
use-global-data-source-stat: true
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
connect-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
mybatis:
# 注意:一定要对应mapper映射xml文件的所在路径
mapper-locations: classpath:/mapper/*Mapper.xml
# 注意:对应实体类的路径
type-aliases-package: com.javaboy.vms.entity
configuration:
map-underscore-to-camel-case: true
# 日志配置
logging:
level:
com.javaboy.vms.mapper: DEBUG
1.3 实体类
package com.javaboy.vms.entity;
import lombok.Getter;
import lombok.Setter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.io.Serializable;
import java.util.Collection;
/**
* 用户信息(VUser)实体类
*
* @author gaoyang
* @since 2021-04-20 14:26:27
*/
@Getter
@Setter
public class VUser implements Serializable, UserDetails {
private static final long serialVersionUID = -60957006911784869L;
/**
* 主键
*/
private Integer id;
/**
* 姓名
*/
private String name;
/**
* 手机号码
*/
private String phone;
/**
* 住宅电话
*/
private String telephone;
/**
* 联系地址
*/
private String address;
/**
* 是否启用
*/
private Boolean enabled;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 头像
*/
private String userface;
/**
* 备注
*/
private String remark;
/**
* 为用户赋予角色
*
* @return
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return enabled;
}
}
1.4 服务实现类
package com.javaboy.vms.service.impl;
import com.javaboy.vms.mapper.VUserMapper;
import com.javaboy.vms.entity.VUser;
import com.javaboy.vms.service.VUserService;
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 javax.annotation.Resource;
import java.util.List;
/**
* 用户信息(VUser)表服务实现类
*
* @author gaoyang
* @since 2021-04-20 14:26:28
*/
@Service("vUserService")
public class VUserServiceImpl implements VUserService, UserDetailsService {
@Resource
private VUserMapper vUserMapper;
/**
* 根据用户名加载用户对象
* @param username
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
VUser user = vUserMapper.loadUserByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("用户名不存在!");
}
return user;
}
}
1.5 xml
<?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.javaboy.vms.mapper.VUserMapper">
<resultMap type="com.javaboy.vms.entity.VUser" id="VUserMap">
<result property="id" column="id" jdbcType="INTEGER"/>
<result property="name" column="name" jdbcType="VARCHAR"/>
<result property="phone" column="phone" jdbcType="VARCHAR"/>
<result property="telephone" column="telephone" jdbcType="VARCHAR"/>
<result property="address" column="address" jdbcType="VARCHAR"/>
<result property="enabled" column="enabled" jdbcType="BOOLEAN"/>
<result property="username" column="username" jdbcType="VARCHAR"/>
<result property="password" column="password" jdbcType="VARCHAR"/>
<result property="userface" column="userface" jdbcType="VARCHAR"/>
<result property="remark" column="remark" jdbcType="VARCHAR"/>
</resultMap>
<!-- 根据用户名查询用户对象 -->
<select id="loadUserByUsername" resultMap="VUserMap">
select
id, name, phone, telephone, address, enabled, username, password, userface, remark
from vms.v_user
where username = #{username}
</select>
</mapper>
1.6 登录项配置
继续完善 SecurityConfig 配置类,重写 configure(HttpSecurity http) 方法:
package com.javaboy.vms.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.javaboy.vms.entity.VUser;
import com.javaboy.vms.service.impl.VUserServiceImpl;
import com.javaboy.vms.util.ResultDTO;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.*;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* @author: gaoyang
* @date: 2021-04-15 16:35
* @description: Spring Security 配置类
*/
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private VUserServiceImpl vUserService;
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(vUserService);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/doLogin")
.usernameParameter("username")
.passwordParameter("password")
// 登录成功回调
.successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
VUser vUser = (VUser) authentication.getPrincipal();
vUser.setPassword(null);
ResultDTO resultDTO = ResultDTO.success("登录成功", vUser);
String s = new ObjectMapper().writeValueAsString(resultDTO);
out.write(s);
out.flush();
out.close();
}
})
// 登录失败回调
.failureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException exception) throws IOException, ServletException {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
ResultDTO resultDTO = ResultDTO.error("登录失败");
if (exception instanceof LockedException) {
resultDTO.setMsg("账户被锁定,请联系管理员!");
} else if (exception instanceof CredentialsExpiredException) {
resultDTO.setMsg("密码过期,请联系管理员!");
} else if (exception instanceof AccountExpiredException) {
resultDTO.setMsg("账户过期,请联系管理员!");
} else if (exception instanceof DisabledException) {
resultDTO.setMsg("账户被禁用,请联系管理员!");
} else if (exception instanceof BadCredentialsException) {
resultDTO.setMsg("用户名或者密码输入错误,请重新输入!");
}
out.write(new ObjectMapper().writeValueAsString(resultDTO));
out.flush();
out.close();
}
})
.permitAll()
.and()
.logout()
// 登出回调
.logoutSuccessHandler(new LogoutSuccessHandler() {
@Override
public void onLogoutSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
out.write(new ObjectMapper().writeValueAsString(ResultDTO.success("注销成功!")));
out.flush();
out.close();
}
})
.permitAll()
.and()
.csrf().disable()
// 没有认证时,在这里处理结果,不要重定向
.exceptionHandling().authenticationEntryPoint(new AuthenticationEntryPoint() {
@Override
public void commence(HttpServletRequest req, HttpServletResponse resp, AuthenticationException e) throws IOException, ServletException {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
ResultDTO resultDTO = ResultDTO.error("访问失败");
if (e instanceof InsufficientAuthenticationException) {
resultDTO.setMsg("请求失败,请联系管理员!");
}
out.write(new ObjectMapper().writeValueAsString(resultDTO));
out.flush();
out.close();
}
});
}
}
其中:
- loginPage 表示定义登录页。
定义了登录页面为 /login 的时候,Spring Security 也会帮我们自动注册一个 /login 的接口,这个接口是 POST 请求,用来处理登录逻辑。
- loginProcessingUrl 指定登录接口地址。
- usernameParameter、passwordParameter 登录参数配置。
登录表单中的参数默认是 username 和 password,如果需要改变登录参数的字段名只需在这里配置即可。
- and 方法表示结束当前标签,上下文回到HttpSecurity,开启新一轮的配置。
- permitAll 表示登录相关的页面/接口不要被拦截。
1.7 登录接口
这里登录不成功返回提示信息。
package com.javaboy.vms.controller;
import com.javaboy.vms.util.ResultDTO;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author: gaoyang
* @date: 2021-04-20 13:23
* @description:
*/
@RestController
public class Login {
/**
* 未登录返回提示信息
* @return
*/
@RequestMapping("/login")
public ResultDTO<Void> login(){
return ResultDTO.error("尚未登录,请登录");
}
}
1.8 登录测试
1. 客户端
web 端代码就不贴了,源码:
https://gitee.com/king-high/vms-master.git
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/5423.html