Spring Boot 2.1.0 整合SpringSecurity 前后端分离

SpringBoot 使用SpringSecurity 前后端分离

文章中使用SpringBoot 和 SpringSecurity ,使用JWT 用来做认证,版本选择是 2.1.0
选择mybatis 用来做 持久化,mysql 8.0。

创建用户角色权限数据库

用户数据库

DROP TABLE IF EXISTS `stack_admin`;
CREATE TABLE `stack_admin`  (
  `id` bigint(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `account` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `email` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `phone` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `address` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `code` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `locked` int(2) NOT NULL DEFAULT 0,
  `last_login_time` bigint(50) NOT NULL,
  `last_password_reset` bigint(50) NOT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `admin_name`(`username`) USING BTREE,
  INDEX `admin_account`(`account`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1153213503822143490 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of stack_admin
-- ----------------------------
INSERT INTO `stack_admin` VALUES (1153213503822143490, 'gssznb', '$2a$10$dQBQGQRkpxsaPgXWfTTWyuCZ66Pno3a8j9fRzBd/HJEh.0FE5dxzK', 'gssznb', '1181954449@qq.com', '15152268067', '江苏省', 'gss', 'xxxx', 1, 1563782499462, 1563782499462);

创建ROLE 表格

-- ----------------------------
-- Table structure for stack_role
-- ----------------------------
DROP TABLE IF EXISTS `stack_role`;
CREATE TABLE `stack_role`  (
  `id` bigint(11) NOT NULL AUTO_INCREMENT,
  `role_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `role_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `role_id`(`id`) USING BTREE,
  INDEX `role_name`(`role_name`) USING BTREE,
  INDEX `in_role_code`(`role_code`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of stack_role
-- ----------------------------
INSERT INTO `stack_role` VALUES (1, '系统管理员', 'ROLE_system');
INSERT INTO `stack_role` VALUES (2, '用户管理', 'ROLE_user');

SET FOREIGN_KEY_CHECKS = 1;

创建关联表格admin_role

-- ----------------------------
-- Table structure for admin_role
-- ----------------------------
DROP TABLE IF EXISTS `admin_role`;
CREATE TABLE `admin_role`  (
  `id` bigint(11) NOT NULL AUTO_INCREMENT,
  `admin_id` bigint(11) NOT NULL,
  `role_id` bigint(11) NOT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `in_admin_id`(`admin_id`) USING BTREE,
  INDEX `in_role_id`(`role_id`) USING BTREE,
  CONSTRAINT `a_r_admin_id` FOREIGN KEY (`admin_id`) REFERENCES `stack_admin` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
  CONSTRAINT `a_r_role_id` FOREIGN KEY (`role_id`) REFERENCES `stack_role` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of admin_role
-- ----------------------------
INSERT INTO `admin_role` VALUES (1, 1153213503822143490, 1);

创建权限表 permission

-- ----------------------------
-- Table structure for stack_permission
-- ----------------------------
DROP TABLE IF EXISTS `stack_permission`;
CREATE TABLE `stack_permission`  (
  `id` bigint(11) NOT NULL,
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `type` int(2) NOT NULL,
  `url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `order` bigint(20) NOT NULL,
  `p_id` bigint(11) NOT NULL,
  `description` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `in_per_name`(`name`) USING BTREE,
  INDEX `in_per_type`(`type`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of stack_permission
-- ----------------------------
INSERT INTO `stack_permission` VALUES (1, '删除用户', 'd', 1, 'admin/d', 1, 0, '无');
INSERT INTO `stack_permission` VALUES (2, '添加用户', 'c', 1, 'admin/c', 1, 0, '无');
INSERT INTO `stack_permission` VALUES (3, '读取用户', 'r', 1, 'admin/r', 1, 0, '无');

创建权限角色关联表格role_permission

DROP TABLE IF EXISTS `role_permission`;
CREATE TABLE `role_permission`  (
  `id` bigint(11) NOT NULL,
  `role_id` bigint(11) NOT NULL,
  `permission_id` bigint(11) NOT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `r_p_role_id`(`role_id`) USING BTREE,
  INDEX `r_p_per_id`(`permission_id`) USING BTREE,
  CONSTRAINT `r_p_per_id` FOREIGN KEY (`permission_id`) REFERENCES `stack_permission` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `r_p_role_id` FOREIGN KEY (`role_id`) REFERENCES `stack_role` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of role_permission
-- ----------------------------
INSERT INTO `role_permission` VALUES (1, 1, 1);
INSERT INTO `role_permission` VALUES (2, 1, 2);
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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>memory</artifactId>
        <groupId>cn.fllday</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>memory-web</artifactId>
    <dependencies>
        <dependency>
            <groupId>cn.fllday</groupId>
            <artifactId>memory-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</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-freemarker</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>

        <!--jwt -->
        <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
        </dependency>

    </dependencies>

</project>
创建model

本文选用 lombok,mybatis-plus .代码生成器,生成实体类,先贴出来生成代码把!

package cn.fllday.generator;

import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

/**
 * @Author:gssznb
 * @Date:Created 2019/7/9 14:37
 */
// 演示例子,执行 main 方法控制台输入模块表名回车自动生成对应项目目录中
public class CodeGenerator {

    /**
     * <p>
     * 读取控制台内容
     * </p>
     */
    public static String scanner(String tip) {
        Scanner scanner = new Scanner(System.in);
        StringBuilder help = new StringBuilder();
        help.append("请输入" + tip + ":");
        System.out.println(help.toString());
        if (scanner.hasNext()) {
            String ipt = scanner.next();
            if (ipt!=null && ipt.length() != 0) {
                return ipt;
            }
        }
        throw new MybatisPlusException("请输入正确的" + tip + "!");
    }

    public static void main(String[] args) {
        // 代码生成器
        AutoGenerator mpg = new AutoGenerator();

        // 全局配置
        GlobalConfig gc = new GlobalConfig();
        String projectPath = System.getProperty("user.dir");
        System.out.println(projectPath);
        gc.setOutputDir("C:\\Users\\MyPC\\Desktop\\memory\\memory-api\\src\\main\\java");
        gc.setAuthor("gssznb");
        gc.setOpen(false);
        // gc.setSwagger2(true); 实体属性 Swagger2 注解
        mpg.setGlobalConfig(gc);

        // 数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://localhost:3306/stack?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8&serverTimezone=GMT%2B8");
        // dsc.setSchemaName("public");
        dsc.setDriverName("com.mysql.cj.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("Root_12root");
        mpg.setDataSource(dsc);

        // 包配置
        PackageConfig pc = new PackageConfig();
        pc.setModuleName(scanner("模块名"));
        pc.setParent("cn.fllday");
        mpg.setPackageInfo(pc);

        // 自定义配置
        InjectionConfig cfg = new InjectionConfig() {
            @Override
            public void initMap() {
                // to do nothing
            }
        };

        // 如果模板引擎是 freemarker
//        String templatePath = "/templates/mapper.xml.ftl";
        // 如果模板引擎是 velocity
        // String templatePath = "/templates/mapper.xml.vm";

        // 自定义输出配置
        List<FileOutConfig> focList = new ArrayList<FileOutConfig>();
        // 自定义配置会被优先输出
//        focList.add(new FileOutConfig(templatePath) {
//            @Override
//            public String outputFile(TableInfo tableInfo) {
//                // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
//                return "Z:\\gssznb\\workspace\\spring-security-learn\\stack-api\\src\\main\\resource\\mapper/" + pc.getModuleName()
//                        + "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
//            }
//        });
        /*
        cfg.setFileCreate(new IFileCreate() {
            @Override
            public boolean isCreate(ConfigBuilder configBuilder, FileType fileType, String filePath) {
                // 判断自定义文件夹是否需要创建
                checkDir("调用默认方法创建的目录");
                return false;
            }
        });
        */
        cfg.setFileOutConfigList(focList);
        mpg.setCfg(cfg);

//        // 配置模板
//        TemplateConfig templateConfig = new TemplateConfig();
//
//        // 配置自定义输出模板
//        //指定自定义模板路径,注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别
//        // templateConfig.setEntity("templates/entity2.java");
//        // templateConfig.setService();
//        // templateConfig.setController();
//
//        templateConfig.setXml(null);
//        mpg.setTemplate(templateConfig);

        // 策略配置
        StrategyConfig strategy = new StrategyConfig();
        strategy.setNaming(NamingStrategy.underline_to_camel);
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);
//        strategy.setSuperEntityClass("cn.fllday.stack.common.BaseEntity");
        strategy.setEntityLombokModel(true);
        strategy.setRestControllerStyle(true);
        // 公共父类
//        strategy.setSuperControllerClass("cn.fllday.stack.common.BaseController");
        // 写于父类中的公共字段
        strategy.setSuperEntityColumns("id");
        strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
        strategy.setControllerMappingHyphenStyle(true);
        strategy.setTablePrefix(pc.getModuleName() + "_");
        mpg.setStrategy(strategy);
//        mpg.setTemplateEngine(new FreemarkerTemplateEngine());
        mpg.execute();
    }

}

实体类
StackAdmin

package cn.fllday.memory.entity;

import java.io.Serializable;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

/**
 * @author gssznb
 * @since 2019-07-22
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class StackAdmin implements Serializable {

    private static final long serialVersionUID=1L;

    private Long id;

    private String username;

    private String password;

    private String account;

    private String email;

    private String phone;

    private String address;

    private String name;

    private String code;

    private Integer locked;

    private Long lastLoginTime;

    private Long lastPasswordReset;
}

StackPermission

package cn.fllday.memory.entity;

import java.io.Serializable;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

/**
 * <p>
 * 
 * </p>
 *
 * @author gssznb
 * @since 2019-07-23
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class StackPermission implements Serializable {

    private static final long serialVersionUID=1L;

    private String name;

    private String code;

    private Integer type;

    private String url;

    private Long order;

    private Long pId;

    private String description;
}

StackRole

package cn.fllday.memory.entity;

import java.io.Serializable;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

/**
 * @author gssznb
 * @since 2019-07-22
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class StackRole implements Serializable {

    private static final long serialVersionUID=1L;

    private Long id;

    private String roleName;

    private String roleCode;
}

Dao层

StackAdminMapper

package cn.fllday.memory.mapper;

import cn.fllday.memory.entity.StackAdmin;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;

/**
 * @author gssznb
 * @since 2019-07-22
 */
@Mapper
@Repository
public interface StackAdminMapper extends BaseMapper<StackAdmin> {

    @Select("select * from stack_admin where username = #{0}")
    StackAdmin selectAdminByUsername(String username);
}

StackPermissionMapper

package cn.fllday.memory.mapper;

import cn.fllday.memory.entity.StackPermission;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import java.util.List;

/**
 * <p>
 *  Mapper 接口
 * </p>
 *
 * @author gssznb
 * @since 2019-07-23
 */
@Mapper
public interface StackPermissionMapper extends BaseMapper<StackPermission> {

    @Select("SELECT * FROM stack_permission AS p LEFT JOIN role_permission AS rp ON p.id = rp.permission_id LEFT JOIN stack_role AS r on r.id = rp.role_id WHERE role_code = #{0}")
    List<StackPermission> selectPermissionByRoleCode(String code);

}

StackRoleMapper

package cn.fllday.memory.mapper;

import cn.fllday.memory.entity.StackRole;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * <p>
 *  Mapper 接口
 * </p>
 *
 * @author gssznb
 * @since 2019-07-22
 */
@Mapper
@Repository
public interface StackRoleMapper extends BaseMapper<StackRole> {

    @Select("select * from stack_role as r left join admin_role  as ar on r.id = ar.role_id left join stack_admin as a on a.id = ar.admin_id where a.id = #{0} ")
    List<StackRole> selectRoleByUserId(Long id);
    
}

Service 层,代码不多,而且一个接口对应一个实现类。就放在一块了
Admin 的 Service

public interface IStackAdminService extends IService<StackAdmin> {

    StackAdmin findAdminByUsername(String username);

    boolean addAdmin(String username,String password);

    String findAdminByUsernamePassword(String username,String password);

    String refreshToken(String token);

}
//  实现类
@Service
public class StackAdminServiceImpl extends ServiceImpl<StackAdminMapper, StackAdmin> implements IStackAdminService {


    @Override
    public StackAdmin findAdminByUsername(String username) {
        return stackAdminMapper.selectAdminByUsername(username);
    }

    @Override
    public boolean addAdmin(String username, String password) {
        StackAdmin stackAdmin = new StackAdmin();
        stackAdmin.setName("gss").setLastPasswordReset(System.currentTimeMillis()).setUsername(username).setAccount(username).setAddress("江苏省").setCode("xxxx").setEmail("1181954449@qq.com").setLastLoginTime(System.currentTimeMillis()).setPhone("15152268067").setLocked(1).setPassword(getPassword(password));
        int result = stackAdminMapper.insert(stackAdmin);
        return result == 1;
    }

    /**
     *  用户名密码
     * @param username
     * @param password
     * @return
     */
    @Override
    public String findAdminByUsernamePassword(String username, String password) {
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
        Authentication authenticate = authenticationManager.authenticate(authenticationToken);
        SecurityContextHolder.getContext().setAuthentication(authenticate);
        UserDetails userDetails = userDetailsService.loadUserByUsername(username);
        String token = jwtTokenUtils.generateToken(userDetails);
        return token;
    }

    @Override
    public String refreshToken(String oldToken) {
        String token = oldToken.substring(tokenHead.length());
        String username = jwtTokenUtils.getUsernameFromToken(token);
        CustomUserDetils custom = (CustomUserDetils) userDetailsService.loadUserByUsername(username);
        if (jwtTokenUtils.canTokenBeRefreshed(token,new Date(custom.getStackAdmin().getLastPasswordReset()))){
            return jwtTokenUtils.refreshToken(token);
        }
        return null;
    }

    private String getPassword(String pwd){
        return new BCryptPasswordEncoder().encode(pwd);

    }
    @Value("${jwt.tokenHead}")
    private String tokenHead;
    @Autowired
    private StackAdminMapper stackAdminMapper;
    private AuthenticationManager authenticationManager;
    private UserDetailsService userDetailsService;
    private JwtTokenUtils jwtTokenUtils;

    @Autowired
    public StackAdminServiceImpl(
            AuthenticationManager authenticationManager,
            UserDetailsService userDetailsService,
            JwtTokenUtils jwtTokenUtils) {
        this.authenticationManager = authenticationManager;
        this.userDetailsService = userDetailsService;
        this.jwtTokenUtils = jwtTokenUtils;
    }

}

这里先不要管这个token 什么的东西。我们先把spring security 整合好

Permission的Service

public interface IStackPermissionService extends IService<StackPermission> {
    List<StackPermission> findPermissionByRoleCode(String code);
}
@Service
public class StackPermissionServiceImpl extends ServiceImpl<StackPermissionMapper, StackPermission> implements IStackPermissionService {

    @Autowired
    private StackPermissionMapper stackPermissionMapper;

    public List<StackPermission> findPermissionByRoleCode(String code){
        return stackPermissionMapper.selectPermissionByRoleCode(code);
    }

}

Role的Service

// 接口
public interface IStackRoleService extends IService<StackRole> {
    List<StackRole> findRoleByUserId(Long id);
}
// 实现类
@Service
public class StackRoleServiceImpl extends ServiceImpl<StackRoleMapper, StackRole> implements IStackRoleService {

    @Autowired
    private StackRoleMapper stackRoleMapper;

    @Override
    public List<StackRole> findRoleByUserId(Long id) {
        return stackRoleMapper.selectRoleByUserId(id);
    }
}

配置SpringSecurity的配置类
package cn.fllday.memory.config.security;

import cn.fllday.memory.config.jwt.JwtAuthenticationEntryPoint;
import cn.fllday.memory.config.jwt.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
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.builders.WebSecurity;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private JwtAuthenticationEntryPoint unauthorizedHandler;
    @Autowired
    private UserDetailsService UserDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(UserDetailsService).passwordEncoder(new BCryptPasswordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
//        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()
//                .antMatchers("auth/**")
//                .permitAll()
//                .anyRequest().authenticated();
                .formLogin().loginPage("/login")
//                .loginProcessingUrl("/login.do")
                // failureUrl() 指定认证失败后Url,defaultSuccessUrl()
                // 指定认证成功后Url。我们可以通过设置 successHandler() 和 failureHandler()
                // 来实现自定义认证成功、失败处理。
                // 当我们设置了这两个后,需要去除 failureUrl() 和 defaultSuccessUrl() 的设置
                // ,否则无法生效。这两套配置同时只能存在一套。
                .defaultSuccessUrl("/login/success")
                .failureUrl("/login/error").permitAll()
                .usernameParameter("username")
                .passwordParameter("password")
                .and()
                .logout().permitAll();
//        http.csrf().disable().exceptionHandling().accessDeniedPage("/stack/authorication");
//        http.addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
        http
                // 由于使用的是JWT,我们这里不需要csrf
                .csrf().disable()
                    
                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()

                // 基于token,所以不需要session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()

                .authorizeRequests()
                //.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                
                // 允许对于网站静态资源的无授权访问
                .antMatchers(
                        HttpMethod.GET,
                        "/",
                        "/*.html",
                        "/favicon.ico",
                        "/**/*.html",
                        "/**/*.css",
                        "/**/*.js"
                ).permitAll()
                // 对于获取token的rest api要允许匿名访问
                .antMatchers("/auth/**")
                .permitAll()
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated();

        // 添加JWT filter
        http
                .addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);

        // 禁用缓存
        http.headers().cacheControl();
    }

    /**
     * 设置静态资源不被拦截
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/css/**","/js/**");
    }

    @Bean
    public DefaultWebSecurityExpressionHandler webSecurityExpressionHandler(){
        DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();
        handler.setPermissionEvaluator(new CustomPermissionEvaluator());
        return handler;
    }

    // 注入刚刚的jwtFilter
    @Bean
    public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter(){
        return new JwtAuthenticationTokenFilter();
    }
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

UserDetailsService UserDetailsService; 这个类是用来根据用户名查询该用户的信息。 这个类需要实现SpringSecurity 中的给我们指定的一个接口 UserDetailsService. 自定义实现这个方法

package cn.fllday.memory.config.security;

import cn.fllday.memory.config.model.CustomRole;
import cn.fllday.memory.config.model.CustomUserDetils;
import cn.fllday.memory.entity.StackAdmin;
import cn.fllday.memory.entity.StackRole;
import cn.fllday.memory.service.IStackAdminService;
import cn.fllday.memory.service.IStackRoleService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
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 java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;

@Slf4j
@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private IStackAdminService iStackAdminService;
    @Autowired
    private IStackRoleService iStackRoleService;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        Collection<GrantedAuthority> authorities = new ArrayList<>();
        // 从数据库中取出数据信息
        if (StringUtils.isEmpty(s)){
            log.info("用户名输入不能为空");
            throw new UsernameNotFoundException("用户名不能为空");
        }
        StackAdmin admin = iStackAdminService.findAdminByUsername(s);
        if (admin == null){
            throw new UsernameNotFoundException("该用户名不存在!");
        }
        List<StackRole> roles = iStackRoleService.findRoleByUserId(admin.getId());
        log.info("用户:{}开始查询对应的角色." , s);
        for (StackRole r: roles ) {
            authorities.add(new CustomRole(r));
        }
        return new CustomUserDetils(admin,new HashSet<>(authorities));
    }
}

这个CustomUserDetailsServiceloadUserByUsername() 方法是用来根据用户名查询用户信息的。 返回一个UserDetails,userDetails是SpringSecurity 提供给我们的一个用户类。我们这边自定义一个并且实现这个类

GrantedAuthority 也是SpringSecurity 提供我们的一个类。

CustomUserDetils 类

package cn.fllday.memory.config.model;

import cn.fllday.memory.entity.StackAdmin;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.Set;

@Data
public class CustomUserDetils implements UserDetails {

    private StackAdmin stackAdmin;

    private Set<GrantedAuthority> authorities;

    public CustomUserDetils(StackAdmin stackAdmin, Set<GrantedAuthority> authorities) {
        this.stackAdmin = stackAdmin;
        this.authorities = authorities;
    }

    // 获取角色的集合
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }
    // 获取密码
    @Override
    public String getPassword() {
        return stackAdmin.getPassword();
    }
    // 获取用户名
    @Override
    public String getUsername() {
        return stackAdmin.getUsername();
    }
    // 帐号是否过期
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
    // 帐号是否锁定
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }
    // 帐号密码是否过期
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
    
    @Override
    public boolean isEnabled() {
        return true;
    }
}

CustomRole

package cn.fllday.memory.config.model;

import cn.fllday.memory.entity.StackRole;
import org.springframework.security.core.GrantedAuthority;

public class CustomRole  implements GrantedAuthority {

    private StackRole stackRole;

    public CustomRole(StackRole stackRole) {
        this.stackRole = stackRole;
    }

    @Override
    public String getAuthority() {
        return this.stackRole.getRoleCode();
    }
}

getAuthority 这个方法返回的是 你的角色代码 就类似于这种ROLE_user

整合JWT

application.yml 配置

server:
  port: 8080
spring:
  application:
    name: memory
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/stack?useUnicode=true&Encoding=utf8&charaterSetResults=utf8&serverTimezone=GMT%2B8
    username: root
    password: Root_12root
  redis:
    host: 192.168.157.129
  main:
    allow-bean-definition-overriding: true
  freemarker:
    suffix: .ftl
    template-loader-path: classpath:/templates
jwt:
  header: Authorization
  secret: mySecret
  expiration: 604800
  tokenHead: "Bearer "
  route:
    authentication:
      path: auth
      refresh: refresh
      register: "auth/register"

JWT的工具类
JwtUtils

package cn.fllday.util;


import cn.fllday.memory.config.model.CustomUserDetils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Component
public class JwtTokenUtils implements Serializable {

    private static final long serialVersionUID = -3301605591108950415L;

    private static final String CLAIM_KEY_USERNAME = "sub";
    private static final String CLAIM_KEY_CREATED = "created";

    @Value("${jwt.secret}")
    private String secret;

    @Value("${jwt.expiration}")
    private Long expiration;

    public String getUsernameFromToken(String token) {
        String username;
        try {
            final Claims claims = getClaimsFromToken(token);
            username = claims.getSubject();
        } catch (Exception e) {
            username = null;
        }
        return username;
    }

    public Date getCreatedDateFromToken(String token) {
        Date created;
        try {
            final Claims claims = getClaimsFromToken(token);
            created = new Date((Long) claims.get(CLAIM_KEY_CREATED));
        } catch (Exception e) {
            created = null;
        }
        return created;
    }

    public Date getExpirationDateFromToken(String token) {
        Date expiration;
        try {
            final Claims claims = getClaimsFromToken(token);
            expiration = claims.getExpiration();
        } catch (Exception e) {
            expiration = null;
        }
        return expiration;
    }

    private Claims getClaimsFromToken(String token) {
        Claims claims;
        try {
            claims = Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            claims = null;
        }
        return claims;
    }

    private Date generateExpirationDate() {
        return new Date(System.currentTimeMillis() + expiration * 1000);
    }

    private Boolean isTokenExpired(String token) {
        final Date expiration = getExpirationDateFromToken(token);
        return expiration.before(new Date());
    }

    private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) {
        return (lastPasswordReset != null && created.before(lastPasswordReset));
    }

    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
        claims.put(CLAIM_KEY_CREATED, new Date());
        return generateToken(claims);
    }

    String generateToken(Map<String, Object> claims) {
        return Jwts.builder()
                .setClaims(claims)
                .setExpiration(generateExpirationDate())
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }

    public Boolean canTokenBeRefreshed(String token, Date lastPasswordReset) {
        final Date created = getCreatedDateFromToken(token);
        return !isCreatedBeforeLastPasswordReset(created, lastPasswordReset)
                && !isTokenExpired(token);
    }

    public String refreshToken(String token) {
        String refreshedToken;
        try {
            final Claims claims = getClaimsFromToken(token);
            claims.put(CLAIM_KEY_CREATED, new Date());
            refreshedToken = generateToken(claims);
        } catch (Exception e) {
            refreshedToken = null;
        }
        return refreshedToken;
    }

    public Boolean validateToken(String token, UserDetails userDetails) {
        CustomUserDetils user = (CustomUserDetils) userDetails;
        final String username = getUsernameFromToken(token);
        final Date created = getCreatedDateFromToken(token);
        //final Date expiration = getExpirationDateFromToken(token);
        return (
                username.equals(user.getUsername())
                        && !isTokenExpired(token)
                        && !isCreatedBeforeLastPasswordReset(created, new Date(user.getStackAdmin().getLastPasswordReset())));
    }
}

JwtAuthenticationTokenFilter类

package cn.fllday.memory.config.jwt;

import cn.fllday.util.JwtTokenUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
@Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private JwtTokenUtils jwtTokenUtils;
    @Value("${jwt.header}")
    private String tokenHeader;
    @Value("${jwt.tokenHead}")
    private String tokenHead;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        String authHeader  = request.getHeader(this.tokenHeader);
        if (authHeader != null && authHeader.startsWith(tokenHead)){
            final String authToken  = authHeader.substring(tokenHead.length());
            String username = jwtTokenUtils.getUsernameFromToken(authToken);
            log.info("用户{}请求",username);
            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                UserDetails user = userDetailsService.loadUserByUsername(username);
                if (jwtTokenUtils.validateToken(authToken,user)){
                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(user,null,user.getAuthorities());
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    logger.info("authenticated user " + username + ", setting security context");
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            }
        }
        chain.doFilter(request,response);
    }
}

JwtAuthenticationEntryPoint的类

package cn.fllday.memory.config.jwt;

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Serializable;

@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {

    private static final long serialVersionUID = -8970718410437077606L;

    @Override
    public void commence(HttpServletRequest request,
                         HttpServletResponse response,
                         AuthenticationException authException) throws IOException {
        // This is invoked when user tries to access a secured REST resource without supplying any credentials
        // We should just send a 401 Unauthorized response because there is no 'login page' to redirect to
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
    }
}
创建Controller 层进行测试

StackAdminController

package cn.fllday.memory.controller;


import cn.fllday.memory.service.IStackAdminService;
import cn.fllday.util.ResponseData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;

/**
 * <p>
 *  前端控制器
 * </p>
 *
 * @author gssznb
 * @since 2019-07-22
 */
@RestController
public class StackAdminController {

    @Autowired
    private IStackAdminService iStackAdminService;
    @GetMapping(value = "test")
    public String test(){
        return "test";
    }

    @PostMapping(value = "add.do")
    public ResponseData add(String username,String password){
        boolean result = iStackAdminService.addAdmin(username, password);
        return result?ResponseData.ok():ResponseData.serverInternalError();
    }

    @RequestMapping(value = "admin/sys")
    @PreAuthorize("hasRole('ROLE_system')")
    public String system(){
        return "如果你能看到这句话,说明你有system角色";
    }

    @RequestMapping(value = "admin/system")
    @PreAuthorize("hasRole('ROLE_user')")
    public String user(){
        return "如果你能看到这句话,说明你有user角色";
    }

    @RequestMapping(value = "admin/r")
    @PreAuthorize("hasPermission('admin/r','r')")
    public String adminR(){
        return "说明你有admin角色r路径权限";
    }
    @RequestMapping(value = "admin/d")
    @PreAuthorize("hasPermission('admin/d','d')")
    public String adminD(){
        return "说明你有admin角色d路径权限";
    }
    @RequestMapping(value = "admin/c")
    @PreAuthorize("hasPermission('admin/c','c')")
    public String adminC(){
        return "说明你有admin角色c路径权限";
    }
}

StackAdminController 测试权限和角色是否可用

CustomSystemController

package cn.fllday.memory.controller;


import cn.fllday.memory.config.jwt.JwtAuthenticationRequest;
import cn.fllday.memory.service.IStackAdminService;
import cn.fllday.util.ResponseData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

@Controller
public class CustomSystemController {
    @Autowired
    private IStackAdminService iStackAdminService;

    @RequestMapping(value = "/login/error")
    @ResponseBody
    public ResponseData error() {
        return ResponseData.userError();
    }

    @GetMapping(value = "login")
    public String login() {
        return "login";
    }

    @GetMapping(value = "system/register")
    @ResponseBody
    public boolean register(String username,String password){
        return iStackAdminService.addAdmin(username,password);
    }

    @GetMapping(value = "/login/success")
    public String success() {
        return "home";
    }

    @GetMapping(value = "/stack/authorication")
    public String authorication(){
        return "authorication";
    }

    @RequestMapping(value = "${jwt.route.authentication.path}",method = RequestMethod.POST)
    @ResponseBody
    public ResponseData loginToken(@RequestBody JwtAuthenticationRequest request){
        String password = request.getPassword();
        String username = request.getUsername();
        String token = iStackAdminService.findAdminByUsernamePassword(username, password);
        return token!=null?ResponseData.ok().putDataValue("token",token):ResponseData.badRequest().putDataValue("token",token);
    }
}

使用POSTMAN 进行测试

使用postman访问 /auth这个接口 进行登录
在这里插入图片描述
查看我们后台的日志打印了什么
在这里插入图片描述
访问需要用户权限的路径
http://localhost:8080/admin/d
在这里插入图片描述
访问的时候需要携带你登录时,返回给你的token。

后台所做的查询操作在这里插入图片描述
这边我们是如果用户携带了token ,那么就会将该用户的用户名再去数据库中查询一次。这个查询也可以不做。 查询成功之后,就再去查询 权限。

如果没有携带token的话,访问。使用postman测试
在这里插入图片描述
可以看到返回 401 错误。没有通过认证。

到这Springboot 整合SpringSecurity 就结束了

项目demo地址

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值