Spring Security入门教程,springboot整合Spring Security

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 securityicon-default.png?t=N7T8https://gitee.com/he-yunlin/springboot-springsecurity.git

猜你喜欢

转载自blog.csdn.net/heyl163_/article/details/133956908