Spring Security是Spring官方推荐的认证、授权框架,功能相比Apache Shiro功能更丰富也更强大,但是使用起来更麻烦。
如果使用过Apache Shiro,学习Spring Security会比较简单一点,两种框架有很多相似的地方。
目录
一、准备工作
创建springboot项目
pom.xml
application.yml
二、创建相关的类
UserDetailsService
SecurityConfig.java
SystemProperties.java
MybatisPlusConfig.java
三、完成登录接口
创建数据库实体类
创建持久层接口
创建登录DTO对象
创建控制器类
创建业务层类
自定义登录成功处理器
一、准备工作
创建springboot项目
首先,通过IntelliJ IDEA创建一个springboot项目,项目名为springboot-springsecurity,在pom.xml中添加相关依赖。
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 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 />
</parent>
<groupId>com.example</groupId>
<artifactId>springboot-springsecurity</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<java.version>1.8</java.version>
<jjwt.version>0.9.1</jjwt.version>
<mysql.version>8.0.28</mysql.version>
<druid.version>1.1.21</druid.version>
<lombok.version>1.18.22</lombok.version>
<mybatis.version>2.2.2</mybatis.version>
<mybatis-plus.version>3.5.1</mybatis-plus.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--validation-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!--spring security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.version}</version>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!--jjwt-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jjwt.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yml
server:
port: 8080
servlet:
context-path: /
spring:
datasource:
username: root
password: root
url: jdbc:mysql://localhost:3306/spring_security
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
mybatis-plus:
mapper-locations: classpath:mapper/*Mapper.xml
logging:
level:
springfox: error
com.example.security: debug
system:
login-page: /login.html
login-url: /user/login
index-page: /index.html
logout-url: /user/logout
parameter:
username: username
password: password
white-url:
- /js/**
- /css/**
- /images/**
- /user/login
- /login.html
二、创建相关的类
UserDetailsService
UserDetailsService接口是Spring Security中非常重要的接口,在登录认证的时候会通过这个接口的loadUserByUsername()方法获取用户的信息,来完成登录的用户名、密码校验,完成登录流程。
我们需要创建一个UserDetailsService的实现类,并声明为Spring组件。
package com.example.security.security;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.security.entity.User;
import com.example.security.exception.GlobalException;
import com.example.security.mapper.UserMapper;
import com.example.security.restful.ResponseCode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
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.Component;
import java.util.ArrayList;
import java.util.List;
/**
* @author heyunlin
* @version 1.0
*/
@Component
public class UserDetailsServiceImpl implements UserDetailsService {
private final UserMapper userMapper;
@Autowired
public UserDetailsServiceImpl(UserMapper userMapper) {
this.userMapper = userMapper;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 查询用户信息
User user = selectByUsername(username);
if (user == null) {
throw new BadCredentialsException("登录失败,用户名不存在!");
} else {
List<String> permissions = selectPermissions(username);
return org.springframework.security.core.userdetails.User.builder()
.username(user.getUsername())
.password(user.getPassword())
.accountExpired(false)
.accountLocked(false)
.disabled(!user.getEnable())
.credentialsExpired(false)
.authorities(permissions.toArray(new String[] {}))
.build();
}
}
/**
* 通过用户名查询用户信息
* @param username 用户名
* @return User
*/
private User selectByUsername(String username) {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("username", username);
List<User> list = userMapper.selectList(wrapper);
if (list.size() == 1) {
return list.get(0);
}
return null;
}
/**
* 通过用户名查询用户权限
* @param username 用户名
* @return List<String>
*/
private List<String> selectPermissions(String username) {
if (username == null) {
throw new GlobalException(ResponseCode.BAD_REQUEST, "用户名不能为空");
}
List<String> permissions = new ArrayList<>();
permissions.add("/user/login");
permissions.add("/user/logout");
permissions.add("/user/selectById");
return permissions;
}
}
SecurityConfig.java
创建security的配置类
package com.example.security.config;
import com.example.security.security.LoginFailHandler;
import com.example.security.security.LoginSuccessHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* @author heyunlin
* @version 1.0
*/
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final SystemProperties systemProperties;
@Autowired
public SecurityConfig(SystemProperties systemProperties) {
this.systemProperties = systemProperties;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new PasswordEncoder() {
@Override
public String encode(CharSequence charSequence) {
return (String) charSequence;
}
@Override
public boolean matches(CharSequence charSequence, String s) {
return charSequence.equals(s);
}
};
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 禁用防跨域攻击
http.csrf().disable();
// 配置各请求路径的认证与授权
http.formLogin()
.loginPage(systemProperties.getLoginPage()) // 自定义登录页面的地址
.loginProcessingUrl(systemProperties.getLoginUrl()) // 处理登录的接口地址
.usernameParameter(systemProperties.getParameter().get("username")) // 用户名的参数名
.passwordParameter(systemProperties.getParameter().get("password")) // 密码的参数名
.successHandler(new LoginSuccessHandler(systemProperties))
//.successForwardUrl("/index.html") // 登录成功跳转的地址
.failureHandler(new LoginFailHandler()); // 登录失败的处理器
// 退出登录相关配置
http.logout()
.logoutUrl(systemProperties.getLogoutUrl()) // 退出登录的接口地址
.logoutSuccessUrl(systemProperties.getLoginUrl()); // 退出登录成功跳转的地址
// 配置认证规则
String[] toArray = systemProperties.getWhiteUrl().toArray(new String[]{});
http.authorizeRequests()
.antMatchers(toArray).permitAll() // 白名单,也就是不需要登录也能访问的资源
.anyRequest().authenticated();
}
}
SystemProperties.java
package com.example.security.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
/**
* @author heyunlin
* @version 1.0
*/
@Data
@Component
@ConfigurationProperties(prefix = "system")
public class SystemProperties {
/**
* 登录页面
*/
private String loginPage;
/**
* 登录的请求地址
*/
private String loginUrl;
/**
* 登录成功后跳转的页面
*/
private String indexPage;
/**
* 退出登录的请求地址
*/
private String logoutUrl;
/**
* 白名单
*/
private List<String> whiteUrl;
/**
* 登录的参数
*/
private Map<String, String> parameter;
}
MybatisPlusConfig.java
package com.example.security.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author heyunlin
* @version 1.0
*/
@Configuration
@MapperScan(basePackages = "com.example.security.mapper")
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 防全表更新与删除插件
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
// 分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
三、完成登录接口
创建数据库实体类
User.java
package com.example.security.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 用户
* @author heyunlin
* @version 1.0
*/
@Data
@TableName("user")
public class User implements Serializable {
private static final long serialVersionUID = 18L;
@TableId(value = "id", type = IdType.INPUT)
private String id;
/**
* 姓名
*/
private String name;
/**
* 性别
*/
private Integer gender;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 手机号
*/
private String phone;
/**
* 是否启用
*/
private Boolean enable;
/**
* 最后一次登录时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime lastLoginTime;
}
创建持久层接口
package com.example.security.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.security.entity.User;
import org.springframework.stereotype.Repository;
/**
* @author heyunlin
* @version 1.0
*/
@Repository
public interface UserMapper extends BaseMapper<User> {
}
创建登录DTO对象
package com.example.security.dto;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
/**
* @author heyunlin
* @version 1.0
*/
@Data
public class UserLoginDTO implements Serializable {
private static final long serialVersionUID = 18L;
/**
* 用户名
*/
@NotNull(message = "用户名不允许为空")
@NotEmpty(message = "用户名不允许为空")
private String username;
/**
* 密码
*/
@NotNull(message = "密码不允许为空")
@NotEmpty(message = "密码不允许为空")
private String password;
}
创建控制器类
package com.example.security.controller;
import com.example.security.dto.UserLoginDTO;
import com.example.security.entity.User;
import com.example.security.restful.JsonResult;
import com.example.security.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* @author heyunlin
* @version 1.0
*/
@RestController
@RequestMapping(path = "/user", produces = "application/json;charset=utf-8")
public class UserController {
private final UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
@RequestMapping(value = "/login", method = RequestMethod.POST)
public JsonResult<Void> login(@Validated UserLoginDTO userLoginDTO) {
userService.login(userLoginDTO);
return JsonResult.success("登录成功");
}
@RequestMapping(value = "/logout", method = RequestMethod.POST)
public JsonResult<Void> logout() {
userService.logout();
return JsonResult.success("登出成功");
}
@RequestMapping(value = "/selectById", method = RequestMethod.GET)
public JsonResult<User> selectById(@RequestParam(value = "id", required = true) String userId) {
User user = userService.selectById(userId);
return JsonResult.success(null, user);
}
}
创建业务层类
UserService接口
package com.example.security.service;
import com.example.security.dto.UserLoginDTO;
import com.example.security.entity.User;
/**
* @author heyunlin
* @version 1.0
*/
public interface UserService {
/**
* 登录认证
* @param userLoginDTO 用户登录信息
*/
void login(UserLoginDTO userLoginDTO);
/**
* 退出登录
*/
void logout();
/**
* 通过ID查询用户信息
* @param userId 用户ID
* @return User 通过ID查询到的用户信息
*/
User selectById(String userId);
}
UserServiceImpl.java
package com.example.security.service.impl;
import com.example.security.dto.UserLoginDTO;
import com.example.security.entity.User;
import com.example.security.mapper.UserMapper;
import com.example.security.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;
/**
* @author heyunlin
* @version 1.0
*/
@Service
public class UserServiceImpl implements UserService {
private final UserMapper userMapper;
private final AuthenticationManager authenticationManager;
@Autowired
public UserServiceImpl(UserMapper userMapper, AuthenticationManager authenticationManager) {
this.userMapper = userMapper;
this.authenticationManager = authenticationManager;
}
@Override
public void login(UserLoginDTO userLoginDTO) {
Authentication authentication = new UsernamePasswordAuthenticationToken(
userLoginDTO.getUsername(),
userLoginDTO.getPassword()
);
authenticationManager.authenticate(authentication);
}
@Override
public void logout() {
// todo
}
@Override
public User selectById(String userId) {
return userMapper.selectById(userId);
}
}
自定义登录成功处理器
登陆成功直接重定向到/index.html
package com.example.security.security;
import com.example.security.config.SystemProperties;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author heyunlin
* @version 1.0
*/
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
private final SystemProperties systemProperties;
public LoginSuccessHandler(SystemProperties systemProperties) {
this.systemProperties = systemProperties;
}
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
response.sendRedirect(systemProperties.getIndexPage());
}
}
至此,springboot整合Spring Security就完成了,项目结构如下。
文章就分享到这里了,代码已开源,可按需获取~
springboot整合spring securityhttps://gitee.com/he-yunlin/springboot-springsecurity.git