Springboot集成SpringSecurity(获取当前登录人)

简言

Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它是用于保护基于Spring的应用程序的实际标准。

Spring Security是一个框架,致力于为Java应用程序提供身份验证和授权。像所有Spring项目一样,Spring Security的真正强大之处在于可以轻松扩展以满足自定义要求(- - - 来自官网翻译)

认证 (authentication) 和授权 (authorization) 的区别

举例:你要登机,你需要出示你的 passport 和 ticket,passport 是为了证明你张三确实是你张三,这就是 authentication;而机票是为了证明你张三确实买了票可以上飞机,这就是 authorization

源码地址:https://gitee.com/wangwenlongGitHub/conformity.git

1:库表设计

用户表


CREATE TABLE `sys_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户id',
  `name` varchar(255) DEFAULT NULL COMMENT '用户名称',
  `age` int(3) DEFAULT NULL COMMENT '用户年龄',
  `sex` char(5) DEFAULT NULL COMMENT '用户性别',
  `phone` varchar(11) DEFAULT NULL COMMENT '用户电话',
  `portrait` varchar(255) DEFAULT NULL COMMENT '用户头像',
  `telephone` varchar(16) DEFAULT NULL COMMENT '座机',
  `address` varchar(64) DEFAULT NULL COMMENT '地址',
  `enabled` tinyint(1) DEFAULT NULL COMMENT '是否启用',
  `username` varchar(255) DEFAULT NULL COMMENT '用户',
  `password` varchar(255) DEFAULT NULL COMMENT '密码',
  `remark` varchar(255) DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='用户表';



角色表


CREATE TABLE `sys_role` (
  `id` bigint(20) NOT NULL COMMENT '编号',
  `office_id` bigint(20) DEFAULT NULL COMMENT '归属机构',
  `name` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '角色名称',
  `enname` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '英文名称',
  `role_type` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '角色类型',
  `data_scope` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '数据范围',
  `is_sys` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '是否系统数据',
  `useable` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '是否可用',
  `create_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '创建者',
  `create_date` datetime NOT NULL COMMENT '创建时间',
  `update_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '更新者',
  `update_date` datetime NOT NULL COMMENT '更新时间',
  `remarks` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '备注信息',
  `del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT '0' COMMENT '删除标记',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表';



权限表


CREATE TABLE `sys_menu` (
  `id` bigint(20) NOT NULL COMMENT '编号',
  `parent_id` bigint(20) NOT NULL COMMENT '父级编号',
  `parent_ids` varchar(2000) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '所有父级编号',
  `name` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '名称',
  `sort` decimal(10,0) NOT NULL COMMENT '排序',
  `icon` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '图标',
  `is_show` char(1) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '是否在菜单中显示',
  `permission` varchar(200) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '权限标识',
  `create_by` bigint(20) NOT NULL COMMENT '创建者',
  `create_date` datetime NOT NULL COMMENT '创建时间',
  `update_by` bigint(20) NOT NULL COMMENT '更新者',
  `update_date` datetime NOT NULL COMMENT '更新时间',
  `remarks` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '备注信息',
  `del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT '0' COMMENT '删除标记',
  `keyval` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'key',
  `type` tinyint(10) NOT NULL DEFAULT '0' COMMENT '菜单类型 0菜单  1按钮',
  `href` varchar(60) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '链接',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='权限表';



用户角色表


CREATE TABLE `sys_user_role` (
  `user_id` bigint(20) NOT NULL COMMENT '用户编号',
  `role_id` bigint(20) NOT NULL COMMENT '角色编号',
  PRIMARY KEY (`user_id`,`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户角色表';


角色权限表

CREATE TABLE `sys_role_menu` (
  `role_id` bigint(20) NOT NULL COMMENT '角色编号',
  `menu_id` bigint(20) NOT NULL COMMENT '菜单编号',
  PRIMARY KEY (`role_id`,`menu_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色权限表';

表关系

表数据

INSERT INTO `sys_user` VALUES (1, '王文龙', 26, '男', '17621298039', 'https://wx.qlogo.cn/mmopen/vi_32/OvXNATSNlFlibKP2U9B3ibYibSSbeMlab9lQcTUqMcPKtBOb51x2EaXjjKUXibIPLia3Mibia8zwZ3EaXHibvzQFa5qJaw/132', NULL, NULL, NULL, 'MrWang', '$2a$10$3Fqf02GaMP4j8PClWrlnPu0goXPRoT7ymGlFZPdP9JFlQNaPoaScC', NULL);
INSERT INTO `sys_user` VALUES (2, '范冰冰', 26, '女', '17621298039', 'https://wx.qlogo.cn/mmopen/vi_32/OvXNATSNlFlibKP2U9B3ibYibSSbeMlab9lQcTUqMcPKtBOb51x2EaXjjKUXibIPLia3Mibia8zwZ3EaXHibvzQFa5qJaw/132', NULL, NULL, NULL, 'fanbingbing', '$2a$10$3Fqf02GaMP4j8PClWrlnPu0goXPRoT7ymGlFZPdP9JFlQNaPoaScC', NULL);




INSERT INTO `sys_role` VALUES (1, NULL, '系统管理员', NULL, NULL, NULL, NULL, NULL, '王文龙', '2020-07-08 10:20:43', '', '2020-07-08 10:21:46', NULL, '1');
INSERT INTO `sys_role` VALUES (2, NULL, '普通用户', NULL, NULL, NULL, NULL, NULL, '王文龙', '2020-07-08 10:20:43', '', '2020-07-08 10:21:46', NULL, '1');



INSERT INTO `sys_menu` VALUES (1, 0, '0', '用户列表', 1, NULL, '1', 'sys:user:findAll', 1, '2020-07-08 10:24:50', 1, '2020-07-08 10:25:13', NULL, '1', '1', 1, NULL);
INSERT INTO `sys_menu` VALUES (2, 0, '0', 'id查询', 1, NULL, '1', 'sys:user:findById', 1, '2020-07-08 10:24:50', 1, '2020-07-08 10:25:13', NULL, '1', '1', 1, NULL);


INSERT INTO `sys_user_role` VALUES (1, 1);
INSERT INTO `sys_user_role` VALUES (2, 2);


INSERT INTO `sys_role_menu` VALUES (1, 1);
INSERT INTO `sys_role_menu` VALUES (1, 2);
INSERT INTO `sys_role_menu` VALUES (2, 2);

2:Pom依赖

 <!--Security依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!-- JWT依赖 -->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-jwt</artifactId>
            <version>1.0.9.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>

3:application.yml 配置

#Tomcat配置
server:
  port: 10922
# # # Mysql数据库配置 # # # # # # Mysql数据库配置 # # # # # # Mysql数据库配置 # # # # # # Mysql数据库配置 # # #
spring:
  # 配置数据源
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/conformity?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false
    username: root
    password: root1234
  #    type: com.alibaba.druid.pool.DruidDataSource
# # # jwt 配置 # # # # # # jwt 配置 # # # # # # jwt 配置 # # # # # # jwt 配置 # # # # # # jwt 配置 # # #
jwt:
  # 密匙KEY
  secret: JWTSecret
  # HeaderKEY
  tokenHeader: Authorization
  # Token前缀字符
  tokenPrefix: Sans-
  # 过期时间 单位秒 1天后过期=86400 7天后过期=604800
  expiration: 86400
  # 配置不需要认证的接口
  antMatchers: /index/**,/login/**,/favicon.ico
# # # mybatis配置 # # # # mybatis配置 # # # # mybatis配置 # # # # mybatis配置 # # # # mybatis配置 # # # # mybatis配置 # #
  ##自动转驼峰
mybatis:
  configuration:
    map-underscore-to-camel-case: true

4:Jwt工具(生成token),及工具类

4.1、jwt配置类

package com.it.conformity.common.config;

import lombok.Getter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * jwt配置类
 *
 * @Author: 王文龙
 * @Date: 2020/7/316:08
 * @Version: 1.0
 * @Describe: 描述:
 */
@Getter
@Component
@ConfigurationProperties(prefix = "jwt")
public class JwtConfig {

//    @Value( "${jwt.secret}")
    public static String secret;
//    @Value("${jwt.tokenHeader}")
    public static String tokenHeader;//HeaderKey
//    @Value( "${jwt.tokenPrefix}")
    public static String tokenPrefix;//Token前缀字符
//    @Value( "${jwt.expiration}")
    public static Integer expiration;//过期时间 单位秒 1天后过期=86400 7天后过期=604800
//    @Value( "${jwt.antMatchers}")
    public static String antMatchers;//配置不需要认证的接口

    public void setSecret(String secret) {
        this.secret = secret;
    }

    public void setTokenHeader(String tokenHeader) {
        this.tokenHeader = tokenHeader;
    }

    public void setTokenPrefix(String tokenPrefix) {
        this.tokenPrefix = tokenPrefix;
    }

    public void setExpiration(Integer expiration) {
        this.expiration = expiration * 1000;
    }

    public void setAntMatchers(String antMatchers) {
        this.antMatchers = antMatchers;
    }

}

4.2、jwt 工具类

package com.it.conformity.common.util;

import com.alibaba.fastjson.JSON;
import com.it.conformity.common.config.JwtConfig;
import com.it.conformity.security.entity.SelfUserEntity;
import com.it.conformity.sys.pojo.SysUser;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;

import java.util.Date;


/**
 * jwt 工具类
 *
 * @Author: 王文龙
 * @Date: 2020/7/314:10
 * @Version: 1.0
 * @Describe: 描述:
 */
@Slf4j
public class JwtTokenUtil {


    /**
     * 生成token
     *
     * @param sysUser 用户实体类
     * @return token
     */
    public static String createToken(SelfUserEntity sysUser) {
        //登录成功生成token

        //Jwts.builder()底层代码 new 了一个 DefaultJwtBuilder 对象,它是wtBuilder的实现类
        String token = Jwts.builder()
                //放入用户名和id
                .setId(sysUser.getUserId() + "")
                //主题
                .setSubject(sysUser.getUsername())
                //签发时间
                .setIssuedAt(new Date())
                //签发者
                .setIssuer(sysUser.getUsername())
                //自定义属性,嵌入用户拥有权限
                .claim("authorities", JSON.toJSONString(sysUser.getAuthorities()))
                //失效时间
                .setExpiration(new Date(System.currentTimeMillis() + JwtConfig.expiration))
                //签名算法和密钥
                .signWith(SignatureAlgorithm.HS512, JwtConfig.secret)
                .compact();

        return token;
    }
}

4.3、无权限处理类

package com.it.conformity.security.handler;

import com.it.conformity.common.util.JsonResultT;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * 无权限 处理类
 *
 * @Author: 王文龙
 * @Date: 2020/7/316:50
 * @Version: 1.0
 * @Describe: 描述:
 */
@Component
public class UserAuthAccessDeniedHandler implements AccessDeniedHandler {


    /**
     * 重写 handle 方法
     *
     * @param httpServletRequest  请求
     * @param httpServletResponse 相应
     * @param e                   异常
     * @throws IOException      Io异常
     * @throws ServletException Servlet异常
     */
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        Map<String, Object> map = new HashMap<>();
        map.put("code", 403);
        map.put("msg", "未授权");
        JsonResultT.responseJson(httpServletResponse, map);
    }
}

4.4、未登录处理类

package com.it.conformity.security.handler;

import com.it.conformity.common.util.JsonResultT;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * 用户未登录处理类
 * @Author: 王文龙
 * @Date: 2020/7/610:06
 * @Version: 1.0
 * @Describe: 描述:
 */
@Component
public class UserAuthenticationEntryPointHandler implements AuthenticationEntryPoint {


    /**
     *
     * @param httpServletRequest 导致了<code>身份验证异常
     * @param httpServletResponse 以便用户代理可以开始身份验证
     * @param e 引发了这场召唤
     * @throws IOException 抛异常
     * @throws ServletException 抛异常
     */
    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        Map<String, Object> map = new HashMap<>();
        map.put("code", 401);
        map.put("msg", "未登录");
        JsonResultT.responseJson(httpServletResponse, map);
    }
}

4.5、登录失败处理类

package com.it.conformity.security.handler;

import com.it.conformity.common.util.JsonResultT;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * 登录失败处理类
 *
 * @Author: 王文龙
 * @Date: 2020/7/610:31
 * @Version: 1.0
 * @Describe: 描述:
 */
@Slf4j
@Component
public class UserLoginFailureHandler implements AuthenticationFailureHandler {


    /**
     * 当身份验证尝试失败时调用
     *
     * @param request   进行身份验证尝试的请求
     * @param response  响应
     * @param exception 为拒绝身份验证而引发的异常
     */
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        //这些对于操作的处理类可以根据不同异常进行不同处理
        if (exception instanceof UsernameNotFoundException) {
            log.info("【登录失败】" + exception.getMessage());
            Map<String, Object> map = new HashMap<>();
            map.put("code", 500);
            map.put("msg", "用户名不存在");
            JsonResultT.responseJson(response, map);
        }
        if (exception instanceof LockedException) {
            log.info("【登陆失败】" + exception.getMessage());
            Map<String, Object> map = new HashMap<>();
            map.put("code", 500);
            map.put("msg", "用户被冻结");
            JsonResultT.responseJson(response, map);
        }
        if (exception instanceof BadCredentialsException) {
            log.info("【登陆失败】" + exception.getMessage());
            Map<String, Object> map = new HashMap<>();
            map.put("code", 500);
            map.put("msg", "用户名密码不正确");
            JsonResultT.responseJson(response, map);
        }
        Map<String, Object> map = new HashMap<>();
        map.put("code", 500);
        map.put("msg", "登录失败");
        JsonResultT.responseJson(response, map);

    }
}

4.6、登录成功处理类

package com.it.conformity.security.handler;

import com.alibaba.fastjson.JSON;
import com.it.conformity.common.config.JwtConfig;
import com.it.conformity.common.util.JwtTokenUtil;
import com.it.conformity.common.util.JsonResultT;
import com.it.conformity.security.entity.SelfUserEntity;
import com.it.conformity.sys.pojo.SysUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * 登录成功处理类
 *
 * @Author: 王文龙
 * @Date: 2020/7/610:44
 * @Version: 1.0
 * @Describe: 描述:
 */
@Slf4j
@Component
public class UserLoginSuccessHandler implements AuthenticationSuccessHandler {


    /**
     * 当用户成功通过身份验证时调用
     *
     * @param request  导致成功身份验证的请求
     * @param response 响应
     * @throws IOException      抛异常
     * @throws ServletException 抛异常
     */
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        // 组装JWT
        SelfUserEntity user =  (SelfUserEntity) authentication.getPrincipal();
        String token = JwtTokenUtil.createToken(user);
        token = JwtConfig.tokenPrefix + token;
        // 封装返回参数
        Map<String, Object> resultData = new HashMap<>();
        resultData.put("code", "200");
        resultData.put("msg", "登录成功");
        resultData.put("token", token);
        JsonResultT.responseJson(response, resultData);

    }
}

4.7、用户登出类

package com.it.conformity.security.handler;

import com.it.conformity.common.util.JsonResultT;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * 用户登出类
 *
 * @Author: 王文龙
 * @Date: 2020/7/610:55
 * @Version: 1.0
 * @Describe: 描述:
 */
@Component
public class UserLogoutSuccessHandler implements LogoutSuccessHandler {


    /**
     * 用户登出返回结果,应该让前台消除token
     *
     * @throws IOException      抛异常
     * @throws ServletException 抛异常
     */
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        Map<String, Object> resultData = new HashMap<>();
        resultData.put("code", "200");
        resultData.put("msg", "登出成功");
        SecurityContextHolder.clearContext();
        JsonResultT.responseJson(response, resultData);
    }
}

5:Security核心类

5.1、SpringSecurity用户的实体

package com.it.conformity.security.entity;

import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.io.Serializable;
import java.util.Collection;


/**
 * SpringSecurity用户的实体
 * 注意:这里必须要实现UserDetails接口
 *
 * @Author: 王文龙
 * @Date: 2020/7/611:43
 * @Version: 1.0
 * @Describe: 描述:
 */
@Data
public class SelfUserEntity implements Serializable, UserDetails {

//    private static final long serialVersionUID = 1L;

    /**
     * 用户ID
     */
    private Integer userId;
    /**
     * 用户名
     */
    private String username;
    /**
     * 密码
     */
    private String password;
    /**
     * 状态:NORMAL正常  PROHIBIT禁用
     */
    private String status;


    /**
     * 用户角色
     */
    private Collection<GrantedAuthority> authorities;
    /**
     * 账户是否过期
     */
    private boolean isAccountNonExpired = false;
    /**
     * 账户是否被锁定
     */
    private boolean isAccountNonLocked = false;
    /**
     * 证书是否过期
     */
    private boolean isCredentialsNonExpired = false;
    /**
     * 账户是否有效
     */
    private boolean isEnabled = true;


    @Override
    public Collection<GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    public boolean isAccountNonExpired() {
        return isAccountNonExpired;
    }

    @Override
    public boolean isAccountNonLocked() {
        return isAccountNonLocked;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return isCredentialsNonExpired;
    }

    @Override
    public boolean isEnabled() {
        return isEnabled;
    }
}

5.2、SpringSecurity用户的业务实现

package com.it.conformity.security.service;

import com.it.conformity.security.entity.SelfUserEntity;
import com.it.conformity.sys.pojo.SysUser;
import com.it.conformity.sys.service.SysUserService;
import org.springframework.beans.BeanUtils;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * SpringSecurity用户的业务实现
 *
 * @Author: 王文龙
 * @Date: 2020/7/611:43
 * @Version: 1.0
 * @Describe: 描述:
 */
@Component
public class SelfUserDetailsServiceImpl implements UserDetailsService {

    @Resource
    private SysUserService sysUserService;


    /**
     * 查询用户信息
     *
     * @param username 用户名
     * @return UserDetails
     * @throws UsernameNotFoundException 抛异常
     */
    @Override
    public SelfUserEntity loadUserByUsername(String username) throws UsernameNotFoundException {
        SysUser userName = sysUserService.findByUserName(username);
        if (userName != null) {
            // 组装参数
            SelfUserEntity selfUserEntity = new SelfUserEntity();
            BeanUtils.copyProperties(userName, selfUserEntity);
            selfUserEntity.setStatus(userName.getEnabled());
            selfUserEntity.setUserId(userName.getId());
            return selfUserEntity;
        }
        return null;
    }
}

5.3、自定义登录验证

package com.it.conformity.security;

import com.it.conformity.security.entity.SelfUserEntity;
import com.it.conformity.security.service.SelfUserDetailsServiceImpl;
import com.it.conformity.sys.dao.SysUserRoleDao;
import com.it.conformity.sys.pojo.SysRole;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * 自定义登录验证
 *
 * @Author: 王文龙
 * @Date: 2020/7/611:17
 * @Version: 1.0
 * @Describe: 描述:
 */
@Component
public class UserAuthenticationProvider implements AuthenticationProvider {


    @Resource
    private SelfUserDetailsServiceImpl selfUserDetailsService;
    @Resource
    private SysUserRoleDao sysUserRoleDao;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        //1、获取表单输入的用户名
        String username = (String) authentication.getPrincipal();
        //2、获取表单输入的密码
        String password = (String) authentication.getCredentials();
        //3、查询用户是否存在
        SelfUserEntity userEntity = selfUserDetailsService.loadUserByUsername(username);
        if (userEntity == null) {
            throw new UsernameNotFoundException("用户名不存在");
        }
        // 我们还要判断密码是否正确,这里我们的密码使用BCryptPasswordEncoder进行加密的
        if (!new BCryptPasswordEncoder().matches(password, userEntity.getPassword())) {
            throw new BadCredentialsException("密码不正确");
        }
        // 还可以加一些其他信息的判断,比如用户账号已停用等判断
        String enabled = "0";
        if (enabled.equals(userEntity.getStatus())) {
            throw new LockedException("该用户已被冻结");
        }
        //查询用户角色
        Set<GrantedAuthority> authorities = new HashSet<>();
        List<SysRole> sysRoles = sysUserRoleDao.selectSysRoleByUserId(userEntity.getUserId());
        for (SysRole sysRole : sysRoles) {
            authorities.add(new SimpleGrantedAuthority("ROLE_" + sysRole.getName()));
        }
        userEntity.setAuthorities(authorities);
        //进行登录
        return new UsernamePasswordAuthenticationToken(userEntity, password, authorities);
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }
}

5.4、自定义权限注解验证

package com.it.conformity.security;

import com.it.conformity.security.entity.SelfUserEntity;
import com.it.conformity.sys.dao.SysMenuDao;
import com.it.conformity.sys.pojo.SysMenu;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.io.Serializable;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * 自定义权限注解验证
 *
 * @Author: 王文龙
 * @Date: 2020/7/615:29
 * @Version: 1.0
 * @Describe: 描述:
 */
@Component
public class UserPermissionEvaluator implements PermissionEvaluator {

    @Resource
    private SysMenuDao sysMenuDao;

    /**
     * hasPermission鉴权方法
     * 这里仅仅判断PreAuthorize注解中的权限表达式
     * 实际中可以根据业务需求设计数据库通过targetUrl和permission做更复杂鉴权
     *
     * @Param authentication  用户身份
     * @Param targetUrl  请求路径
     * @Param permission 请求路径权限
     * @Return boolean 是否通过
     */
    @Override
    public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
        //获取用户信息
        SelfUserEntity selfUserEntity = (SelfUserEntity) authentication.getPrincipal();
        // 查询用户权限(这里可以将权限放入缓存中提升效率)
        Set<String> permissions = new HashSet<>();
        List<SysMenu> sysMenus = sysMenuDao.selectMenuByUserId(selfUserEntity.getUserId());
        for (SysMenu sysMenu : sysMenus) {
            permissions.add(sysMenu.getPermission());
        }
        // 权限对比
        if (permissions.contains(permission.toString())) {
            return true;
        }
        return false;
    }

    /**
     * Alternative method for evaluating a permission where only the identifier of the
     * target object is available, rather than the target instance itself.
     *
     * @param authentication represents the user in question. Should not be null.
     * @param targetId       the identifier for the object instance (usually a Long)
     * @param targetType     a String representing the target's type (usually a Java
     *                       classname). Not null.
     * @param permission     a representation of the permission object as supplied by the
     *                       expression system. Not null.
     * @return true if the permission is granted, false otherwise
     */
    @Override
    public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
        return false;
    }
}

5.5、SpringSecurity核心配置类

package com.it.conformity.common.config;

import com.it.conformity.security.UserAuthenticationProvider;
import com.it.conformity.security.UserPermissionEvaluator;
import com.it.conformity.security.handler.*;
import com.it.conformity.security.jwt.JwtAuthenticationTokenFilter;
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.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;

import javax.annotation.Resource;

/**
 * SpringSecurity核心配置类
 *
 * @Author: 王文龙
 * @Date: 2020/7/714:47
 * @Version: 1.0
 * @Describe: 描述:
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) //开启权限注解,默认是关闭的
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private UserLoginSuccessHandler userLoginSuccessHandler;//自定义成功处理类
    @Resource
    private UserLoginFailureHandler userLoginFailureHandler;//自定义登录失败处理类
    @Resource
    private UserLogoutSuccessHandler userLogoutSuccessHandler;//自定义注销成功处理类
    @Resource
    private UserAuthAccessDeniedHandler userAuthAccessDeniedHandler;//自定义无权访问处理类
    @Resource
    private UserAuthenticationEntryPointHandler userAuthenticationEntryPointHandler;//自定义用户未登录处理类
    @Resource
    private UserAuthenticationProvider userAuthenticationProvider;//自定义登录验证

    /**
     * 加密方式
     *
     * @return BCryptPasswordEncoder
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 注入自定义 permissionEvaluator
     *
     * @return handler
     */
    @Bean
    public DefaultWebSecurityExpressionHandler userSecurityExpressionHandler() {
        DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();
        handler.setPermissionEvaluator(new UserPermissionEvaluator());
        return handler;
    }

    /**
     * 配置登录验证逻辑
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) {
        //这里可启用我们自己的登陆验证逻辑
        auth.authenticationProvider(userAuthenticationProvider);
    }

    /**
     * 配置security的控制逻辑
     * @param http http 请求
     * @throws Exception 异常
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                //不进行权限验证的请求或资源(从配置文件中读取)
                .antMatchers(JwtConfig.antMatchers.split(",")).permitAll()
                //其他的需要登陆后才能访问
                .anyRequest().authenticated()
                .and()
                //配置未登录自定义处理类
                .httpBasic().authenticationEntryPoint(userAuthenticationEntryPointHandler)
                .and()
                //配置登录地址
                .formLogin()
                .loginProcessingUrl("/login/userLogin")
                //配置登录成功自定义处理类
                .successHandler(userLoginSuccessHandler)
                //配置登录失败自定义处理类
                .failureHandler(userLoginFailureHandler)
                .and()
                //配置登出地址
                .logout()
                .logoutUrl("/login/userLogout")
                //配置用户登出自定义处理类
                .logoutSuccessHandler(userLogoutSuccessHandler)
                .and()
                //配置没有权限自定义处理类
                .exceptionHandling().accessDeniedHandler(userAuthAccessDeniedHandler)
                .and()
                // 开启跨域
                .cors()
                .and()
                // 取消跨站请求伪造防护
                .csrf().disable();
        // 基于Token不需要session
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        // 禁用缓存
        http.headers().cacheControl();
        // 添加JWT过滤器
        http.addFilter(new JwtAuthenticationTokenFilter(authenticationManager()));
    }
}

5.6、JWT接口请求校验过滤器

package com.it.conformity.security.jwt;

import com.alibaba.fastjson.JSONObject;
import com.it.conformity.common.config.JwtConfig;
import com.it.conformity.security.entity.SelfUserEntity;
import com.it.conformity.common.util.StringUtils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * JWT接口请求校验过滤器
 * @Author: 王文龙
 * @Date: 2020/7/715:12
 * @Version: 1.0
 * @Describe: 描述:
 */
@Slf4j
public class JwtAuthenticationTokenFilter extends BasicAuthenticationFilter {

    public JwtAuthenticationTokenFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 获取请求头中JWT的Token
        String tokenHeader = request.getHeader(JwtConfig.tokenHeader);
//        startsWith方法是String的方法,用于检查字符串以指定的前缀开头
        if (null!=tokenHeader && tokenHeader.startsWith(JwtConfig.tokenPrefix)) {
            try {
                // 截取JWT前缀
                String token = tokenHeader.replace(JwtConfig.tokenPrefix, "");
                // 解析JWT
                Claims claims = Jwts.parser()
                        .setSigningKey(JwtConfig.secret)//base64加密
                        .parseClaimsJws(token)
                        .getBody();
                // 获取用户名
                String username = claims.getSubject();
                String userId=claims.getId();
                if(!StringUtils.isEmpty(username)&&!StringUtils.isEmpty(userId)) {
                    // 获取角色
                    List<GrantedAuthority> authorities = new ArrayList<>();
                    String authority = claims.get("authorities").toString();
                    if(!StringUtils.isEmpty(authority)){
                        List<Map<String,String>> authorityMap = JSONObject.parseObject(authority, List.class);
                        for(Map<String,String> role : authorityMap){
                            if(!org.springframework.util.StringUtils.isEmpty(role)) {
                                authorities.add(new SimpleGrantedAuthority(role.get("authority")));
                            }
                        }
                    }
                    //组装参数
                    SelfUserEntity selfUserEntity = new SelfUserEntity();
                    selfUserEntity.setUsername(claims.getSubject());
                    selfUserEntity.setUserId(Integer.parseInt(claims.getId()));
                    selfUserEntity.setAuthorities(authorities);
                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(selfUserEntity, userId, authorities);
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            } catch (ExpiredJwtException e){
                log.info("Token过期");
            } catch (Exception e) {
                log.info("Token无效");
            }
        }
        filterChain.doFilter(request, response);
        return;
    }
}

6、Security流程图

1、SpringSecurity核心配置(安全认证策略)

2、登录认证

3、权限验证

7、测试

到此为止上面的配置就算是结束了,现在可以测试了,还有一点值得提醒的是,Springboot的启动类,没用的扫描或注解最好去掉,不然会报错

1、登录测试

2、接口测试

这里以 findById和findAll俩接口进行测试,库表中有两个用户,两个角色,两个权限

关系:

用户 1,管理员,可以同时访问findById和findAll两个接口(相当于最大权限)

用户 2,普通用户,只可访问findById接口,如果访问findAll接口 则会提示 权限不足

添加权限:

需要在接口上添加@PreAuthorize注解,将权限的permission信息配置到注解中即可

看到这一步,你也应该跑起来,并且实现了吧,剩下一点小尾巴,来说一下获取当前登录人

SpringSecurity的核心组件是SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保存在SecurityContextHolder中。SecurityContextHolder默认使用ThreadLocal 策略来存储认证信息。看到ThreadLocal 也就意味着,这是一种与线程绑定的策略。Spring Security在用户登录时自动绑定认证信息到当前线程,在用户退出时,自动清除当前线程的认证信息

自定义UserUtils

package com.it.conformity.common.util;

import com.it.conformity.security.entity.SelfUserEntity;
import com.it.conformity.sys.dao.SysUserDao;
import com.it.conformity.sys.pojo.SysUser;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * @Author: 王文龙
 * @Date: 2020/7/916:30
 * @Version: 1.0
 * @Describe: 描述:
 */
@Component
public class UserUtils {

    @Resource
    public SysUserDao sysUserDao;

    public static SysUser getUser() {
        SelfUserEntity sysUser = (SelfUserEntity)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        return sysUserDao.findById(sysUser.getUserId());
    }
}

用法

SysUser user = UserUtils.getUser();

猜你喜欢

转载自blog.csdn.net/qq_42227281/article/details/107252956