JSP、SpringMVC(注解形式)、SpringSecurity(自定义)和MyBatis(MySQL+Druid)整合笔记

记录一下近期学习 SpringSecurity 第一阶段的笔记和心得,并与之前的学习框架进行整合,有不妥之处,忘大佬们指出!

目录

1、数据库构建

2、通过注解形式,搭建SpringMVC框架

2.1、通过Maven创建一个project

2.2、在pom文件中添加依赖(包括本文章所有阶段的依赖)

2.3、利用Add Framework Support添加SpringMVC

2.4、创建JavaConfig注解配置类

2.5、添加测试 controller 和 JSP

2.6、此时可以将项目添加至 tomcat 中进行测试

3、整合 MyBatis 和 Druid

3.1、在 pom 文件中添加依赖

2.2节,MyBatis、MySql 和 Druid连接池等依赖已经添加

3.2 添加数据库 properties 配置文件

3.3、利用 JavaConfig 配置 MyBatis

3.4、利用 MyBatis Generator 自动生成接口文件和 xml 文件

4、整合 SpringSecurity 登录验证和权限分配

4.1、将 MyBatis 自动生成 bean 改造

4.2、创建 MyUserDetailsService 类

4.3、利用JavaConfig配置

4.4、自定义拦截器 MyFilterSecurityInterceptor

4.5、自定义资源获取类 MyFilterInvocationSecurityMetadataSource

4.6、自定义决策器 MyAccessDecisionManager

4.7、请求 SecurityController

4.8、JSP

5、最后贴上整个项目的构成图

6、参考文献(博文)


1、数据库构建

构建sql语句及其对应说明

##用户表
CREATE TABLE `user` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) NOT NULL,
`password` varchar(255) NOT NULL,
PRIMARY KEY (`id`) 
);

##角色表
CREATE TABLE `role` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`) 
);

##角色用户关联表
CREATE TABLE `user_role` (
`user_id` bigint(11) NOT NULL,
`role_id` bigint(11) NOT NULL
);

##权限表
CREATE TABLE `permission` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`url` varchar(255) NOT NULL,
`name` varchar(255) NOT NULL,
`description` varchar(255) NULL
PRIMARY KEY (`id`) 
);

##角色权限关联表
CREATE TABLE `role_permission` (
`role_id` bigint(11) NOT NULL,
`permission_id` bigint(11) NOT NULL
);

##构建两个用户,password与username相同,md5加密
INSERT INTO user (id, username, password) VALUES (1,'user','ee11cbb19052e40b07aac0ca060c23ee'); 
INSERT INTO user (id, username , password) VALUES (2,'admin','21232f297a57a5a743894a0e4a801fc3'); 

##构建两个角色
INSERT INTO role (id, name) VALUES (1,'USER');
INSERT INTO role (id, name) VALUES (2,'ADMIN');

##构建权限
INSERT INTO permission (id, url, name, pid) VALUES (1,'/user/common','common',0);
INSERT INTO permission (id, url, name, pid) VALUES (2,'/user/admin','admin',0);

##用户角色关系表
INSERT INTO user_role (user_id, role_id) VALUES (1, 1);
INSERT INTO user_role (user_id, role_id) VALUES (2, 1);
INSERT INTO user_role (user_id, role_id) VALUES (2, 2);

##构建角色权限关系表
INSERT INTO role_permission (role_id, permission_id) VALUES (1, 1);
INSERT INTO role_permission (role_id, permission_id) VALUES (2, 1);
INSERT INTO role_permission (role_id, permission_id) VALUES (2, 2);

2、通过注解形式,搭建SpringMVC框架

2.1、通过Maven创建一个project

2.2、在pom文件中添加依赖(包括本文章所有阶段的依赖)

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

  <dependencies>
    <!--**********JunitTest**********-->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>

    <!--**********SpringMVC**********-->
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-web -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>5.2.0.RELEASE</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.2.0.RELEASE</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.0.RELEASE</version>
    </dependency>

    <!--**********jstl**********-->
    <!-- https://mvnrepository.com/artifact/javax.servlet.jsp.jstl/jstl -->
    <dependency>
      <groupId>javax.servlet.jsp.jstl</groupId>
      <artifactId>jstl</artifactId>
      <version>1.2</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/javax.servlet.jsp.jstl/jstl-api -->
    <dependency>
      <groupId>javax.servlet.jsp.jstl</groupId>
      <artifactId>jstl-api</artifactId>
      <version>1.2</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.glassfish.web/jstl-impl -->
    <dependency>
      <groupId>org.glassfish.web</groupId>
      <artifactId>jstl-impl</artifactId>
      <version>1.2</version>
    </dependency>

    <!--**********日志工具**********-->
    <!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
    <dependency>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
        <version>1.2</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-core</artifactId>
      <version>2.12.1</version>
    </dependency>

    <!--**********MySQL驱动类**********-->
    <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.18</version>
    </dependency>

    <!--**********Alibaba数据库连接池类**********-->
    <!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.21</version>
    </dependency>

    <!--**********lombok**********-->
    <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.10</version>
      <scope>provided</scope>
    </dependency>

    <!--**********mybatis**********-->
    <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.4.1</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>2.0.3</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.mybatis.generator/mybatis-generator-core -->
    <dependency>
      <groupId>org.mybatis.generator</groupId>
      <artifactId>mybatis-generator-core</artifactId>
      <version>1.4.0</version>
    </dependency>

    <!--**********servlet工具类**********-->
    <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>4.0.1</version>
    </dependency>

2.3、利用Add Framework Support添加SpringMVC

2.4、创建JavaConfig注解配置类

WebAppInitializer(后期添加 SpringSecurity 框架时,会对其进行修改)
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[]{RootConfig.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[]{WebConfig.class};
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}
WebConfig
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

@Configuration
@EnableWebMvc
@ComponentScan("com.zjst.tc.controller")
public class WebConfig implements WebMvcConfigurer {

    /**
     * 视图解析器
     * @return
     */
    @Bean
    public ViewResolver viewResolver(){
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix("/WEB-INF/views/");
        viewResolver.setSuffix(".jsp");
        viewResolver.setExposeContextBeansAsAttributes(true);
        return viewResolver;
    }

    /**
     * 配置静态资源处理
     * @param configurer
     */
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();//将静态资源的请求转发到servlet容器中默认的servlet上
    }
}
RootConfig(暂时为空,后续会添加 druid 和 mybatis 配置)
@Configuration
@ComponentScan(basePackages = {"com.zjst.tc"},
        excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, EnableWebMvc.class})},
        includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Service.class, Repository.class, Component.class})}
)
public class RootConfig implements EmbeddedValueResolverAware {

}

2.5、添加测试 controller 和 JSP

SuccessController

@Controller
public class SuccessController {

    @RequestMapping(value="/success",method = RequestMethod.GET)
    public String success(){
        return "success";
    }
}

index.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
<body>
    <a href="/success">test</a>
</body>
</html>

success.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h1>this is success page</h1>
</body>
</html>

2.6、此时可以将项目添加至 tomcat 中进行测试

index 页面成功跳转 success 页面即为成功,当然也可以引入 MockMVC 进行测试

 

3、整合 MyBatis 和 Druid

3.1、在 pom 文件中添加依赖

2.2节,MyBatis、MySql 和 Druid连接池等依赖已经添加

3.2 添加数据库 properties 配置文件

各配置项的意义可以参考:https://www.cnblogs.com/wuyun-blog/p/5679073.html

dbConfig.properties

type=com.alibaba.druid.pool.DruidDataSource
user=root
password=1230
jdbcUrl=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF8&serverTimezone=UTC&useSSL=false
driverClass=com.mysql.cj.jdbc.Driver

##初始连接数量
initialSize=5

##最小空闲数量
minIdle=5

##最大连接数量
maxActive=20

##最大等待毫秒数
maxWait=60000

timeBetweenEvictionRunsMillis=60000

minEvictableIdleTimeMillis=300000

##用来检测连接是否有效的sql,要求是一个查询语句
validationQuery=SELECT 1 FROM DUAL

##申请连接的时候检测,如果空闲时间大于#timeBetweenEvictionRunsMillis,#执行validationQuery检测连接是否有效。
testWhileIdle=true

##申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
testOnBorrow=false

##归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
testOnReturn=false

poolPreparedStatements=true
maxPoolPreparedStatementPerConnectionSize=20

filters=stat

connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000

3.3、利用 JavaConfig 配置 MyBatis

在2.4节的RootConfig中添加,实现了 EmbeddedValueResolverAware 接口,用于将 properties 中的配置项引入,也可使用 @Value("...")注解将配置项引入

import com.alibaba.druid.pool.DruidDataSource;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.context.annotation.*;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.util.StringValueResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

@Configuration
@MapperScan("com.zjst.tc.repository.mappers")//用于扫描mybatis的mapper接口文件
@ComponentScan(basePackages = {"com.zjst.tc"},
        excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, EnableWebMvc.class})},
        includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Service.class, Repository.class, Component.class})}
)
@PropertySource("classpath:/dbConfig.properties")
public class RootConfig implements EmbeddedValueResolverAware {

    private StringValueResolver stringValueResolver;

    /**
     * mybatis配置
     *
     * @return
     */
    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean() throws IOException {
        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();//mybatis-plus插件类

        //配置数据源
        sqlSessionFactoryBean.setDataSource(dataSource());
        //配置mapper文件位置
        sqlSessionFactoryBean.setMapperLocations(resourcePatternResolver.getResources("classpath:mappers/*.xml"));
        //实体类所在的包
        sqlSessionFactoryBean.setTypeAliasesPackage("com.zjst.tc.bean");

        return sqlSessionFactoryBean;
    }

    /**
     * 配置druid数据库连接池
     * @return
     */
    @Bean
    public DataSource dataSource() {

        com.alibaba.druid.pool.DruidDataSource dataSource = new DruidDataSource();
        try {
            dataSource.setUsername(stringValueResolver.resolveStringValue("${user}"));
            dataSource.setPassword(stringValueResolver.resolveStringValue("${password}"));
            dataSource.setUrl(stringValueResolver.resolveStringValue("${jdbcUrl}"));
            dataSource.setDriverClassName(stringValueResolver.resolveStringValue("${driverClass}"));

            dataSource.setInitialSize(Integer.parseInt(stringValueResolver.resolveStringValue("${initialSize}")));
            dataSource.setMinIdle(Integer.parseInt(stringValueResolver.resolveStringValue("${minIdle}")));
            dataSource.setMaxActive(Integer.parseInt(stringValueResolver.resolveStringValue("${maxActive}")));
            dataSource.setMaxWait(Integer.parseInt(stringValueResolver.resolveStringValue("${maxWait}")));
            dataSource.setTimeBetweenEvictionRunsMillis(Integer.parseInt(stringValueResolver.resolveStringValue("${timeBetweenEvictionRunsMillis}")));
            dataSource.setMinEvictableIdleTimeMillis(Integer.parseInt(stringValueResolver.resolveStringValue("${minEvictableIdleTimeMillis}")));
            dataSource.setValidationQuery(stringValueResolver.resolveStringValue("${validationQuery}"));
            dataSource.setTestWhileIdle(Boolean.parseBoolean(stringValueResolver.resolveStringValue("${testWhileIdle}")));
            dataSource.setTestOnBorrow(Boolean.parseBoolean(stringValueResolver.resolveStringValue("${testOnBorrow}")));
            dataSource.setTestOnReturn(Boolean.parseBoolean(stringValueResolver.resolveStringValue("${testOnReturn}")));
            dataSource.setPoolPreparedStatements(Boolean.parseBoolean(stringValueResolver.resolveStringValue("${poolPreparedStatements}")));
            dataSource.setMaxPoolPreparedStatementPerConnectionSize(Integer.parseInt(stringValueResolver.resolveStringValue("${maxPoolPreparedStatementPerConnectionSize}")));

            dataSource.setFilters(stringValueResolver.resolveStringValue("${filters}"));
            dataSource.setConnectionProperties(stringValueResolver.resolveStringValue("${connectionProperties}"));

        } catch (Exception e) {
            System.out.println(e.getMessage());
        }

        return dataSource;
    }

    @Override
    public void setEmbeddedValueResolver(StringValueResolver stringValueResolver) {
        this.stringValueResolver = stringValueResolver;
    }
}

3.4、利用 MyBatis Generator 自动生成接口文件和 xml 文件

暂时在test中添加 TestMybatisGenerator 类用于配置文件类

import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.config.*;
import org.mybatis.generator.internal.DefaultShellCallback;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class TestMybatisGenerator {

    public static void main(String[] args) throws Exception {

        Context context = new Context(ModelType.CONDITIONAL);
        context.setTargetRuntime("MyBatis3");
        context.setId("defaultContext");

        //自动识别数据库关键字,默认false,如果设置为true,
        //根据SqlReservedWords中定义的关键字列表;一般保留默认值,遇到数据库关键字(Java关键字),
        //使用columnOverride覆盖
        context.addProperty("autoDelimitKeywords", "true");

        //生成的Java文件的编码
        context.addProperty("javaFileEncoding", "utf-8");
        context.addProperty("beginningDelimiter", "`");
        context.addProperty("endingDelimiter", "`");

        //格式化java代码
        context.addProperty("javaFormatter", "org.mybatis.generator.api.dom.DefaultJavaFormatter");
        //格式化xml代码
        context.addProperty("xmlFormatter", "org.mybatis.generator.api.dom.DefaultXmlFormatter");
        //格式化信息
        PluginConfiguration pluginConfiguration = new PluginConfiguration();
        pluginConfiguration.setConfigurationType("org.mybatis.generator.plugins.SerializablePlugin");
        pluginConfiguration.setConfigurationType("org.mybatis.generator.plugins.ToStringPlugin");
        context.addPluginConfiguration(pluginConfiguration);

        //设置是否去除注释
        CommentGeneratorConfiguration commentGeneratorConfiguration = new CommentGeneratorConfiguration();
        commentGeneratorConfiguration.addProperty("suppressAllComments", "true");
        //commentGeneratorConfiguration.addProperty("suppressDate","true");
        context.setCommentGeneratorConfiguration(commentGeneratorConfiguration);

        //设置连接数据库
        JDBCConnectionConfiguration jdbcConnectionConfiguration = new JDBCConnectionConfiguration();
        jdbcConnectionConfiguration.setDriverClass("com.mysql.cj.jdbc.Driver");
        jdbcConnectionConfiguration.setConnectionURL("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF8&serverTimezone=UTC&useSSL=false&nullCatalogMeansCurrent=true");
        jdbcConnectionConfiguration.setPassword("1230");
        jdbcConnectionConfiguration.setUserId("root");
        context.setJdbcConnectionConfiguration(jdbcConnectionConfiguration);

        JavaTypeResolverConfiguration javaTypeResolverConfiguration = new JavaTypeResolverConfiguration();
        //是否使用bigDecimal, false可自动转化以下类型(Long, Integer, Short, etc.)
        javaTypeResolverConfiguration.addProperty("forceBigDecimals", "false");
        context.setJavaTypeResolverConfiguration(javaTypeResolverConfiguration);

        //生成实体类(bean)的路径
        JavaModelGeneratorConfiguration javaModelGeneratorConfiguration = new JavaModelGeneratorConfiguration();
        javaModelGeneratorConfiguration.setTargetProject("src/main/java");
        javaModelGeneratorConfiguration.setTargetPackage("com.zjst.tc.bean");
        javaModelGeneratorConfiguration.addProperty("enableSubPackages", "true");
        javaModelGeneratorConfiguration.addProperty("trimStrings", "true");
        context.setJavaModelGeneratorConfiguration(javaModelGeneratorConfiguration);

        //生成的xml的路径
        SqlMapGeneratorConfiguration sqlMapGeneratorConfiguration = new SqlMapGeneratorConfiguration();
        sqlMapGeneratorConfiguration.setTargetProject("src/main/resource");
        sqlMapGeneratorConfiguration.setTargetPackage("mappers");
        sqlMapGeneratorConfiguration.addProperty("enableSubPackages", "true");
        context.setSqlMapGeneratorConfiguration(sqlMapGeneratorConfiguration);

        //生成注解接口的路径
        JavaClientGeneratorConfiguration javaClientGeneratorConfiguration = new JavaClientGeneratorConfiguration();
        javaClientGeneratorConfiguration.setTargetProject("src/main/java");
        javaClientGeneratorConfiguration.setTargetPackage("com.zjst.tc.repository");
        //XMLMAPPER:sql写在xml中;ANNOTATEDMAPPER:sql写在注解中
        javaClientGeneratorConfiguration.setConfigurationType("XMLMAPPER");
        javaClientGeneratorConfiguration.addProperty("enableSubPackages", "true");
        context.setJavaClientGeneratorConfiguration(javaClientGeneratorConfiguration);

        //指定需要生成的表
        TableConfiguration tableConfiguration = new TableConfiguration(context);
        tableConfiguration.setTableName("role_permission");
        tableConfiguration.setGeneratedKey(new GeneratedKey("id", "Mysql", true, null));
        tableConfiguration.setCountByExampleStatementEnabled(true);
        tableConfiguration.setUpdateByExampleStatementEnabled(true);
        tableConfiguration.setDeleteByExampleStatementEnabled(true);
        tableConfiguration.setInsertStatementEnabled(true);
        tableConfiguration.setDeleteByPrimaryKeyStatementEnabled(true);
        //Mysql无法正常支持SQL catalogs 和 schema
        //tableConfiguration.setCatalog();
        //tableConfiguration.setSchema("public");
        context.addTableConfiguration(tableConfiguration);

        List<String> warnings = new ArrayList<>();//用于装异常信息
        MyBatisGenerator myBatisGenerator;
        try {
            Configuration config = new Configuration();
            config.addContext(context);
            config.validate();
            boolean overwrite = true;
            DefaultShellCallback callback = new DefaultShellCallback(overwrite);
            myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
            myBatisGenerator.generate(null);

        } catch (Exception e) {
            System.out.println("--->" + e.getMessage());
        }

        //若有异常警告,则在控制台打印
        if (warnings.size() > 0) {
            System.out.println("===>" + Arrays.asList(warnings));
        }
    }
}

此时会在指定的包中生成所需的文件

 

4、整合 SpringSecurity 登录验证和权限分配

4.1、将 MyBatis 自动生成 bean 改造

User 类实现 SpringSecurity 的 UserDetail 接口类,并添加 authorities 属性,用于装用户的角色 Role 对象,后续验证权限时使用(这个很重要)

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

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

public class User implements UserDetails, Serializable {
    private Integer id;
    private String username;
    private String password;

    /**
     * 装用户的角色
     */
    private List<Role> authorities;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username == null ? null : username.trim();
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {

        this.password = password == null ? null : password.trim();
    }

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

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

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

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

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

    public void setAuthorities(List<Role> authorities) {
        this.authorities = authorities;
    }
}

Role 类实现 SpringSecurity 的 GrantedAuthority 接口,重写 getAuthority 方法,返回 roleName(不是Role对象),用于后续的权限校验(这个很重要)

import org.springframework.security.core.GrantedAuthority;

public class Role implements GrantedAuthority {
    private Integer id;
    private String name;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String getAuthority() {
        return name;
    }
}

添加 RolePermissionDetail 类,用于装角色和权限的对应关系

import lombok.Data;

@Data
public class RolePermissionDetail {
    private Integer roleId;
    private String roleName;
    private Integer permissionId;
    private String permissionName;
    private String permissionUrl;
}

 

4.2、创建 MyUserDetailsService 类

实现 SpringSecurity 的UserDetailsService 接口,重写 loadUserByUsername 方法,用于登录时,利用userName 获取用户的基本信息和角色信息(List),在用户请求某一受限路径的时候,用于匹配用户是否有足够的权限进行访问。

import com.mysql.cj.util.StringUtils;
import com.zjst.tc.bean.*;
import com.zjst.tc.repository.mappers.RoleMapper;
import com.zjst.tc.repository.mappers.UserMapper;
import com.zjst.tc.repository.mappers.UserRoleMapper;
import org.springframework.beans.factory.annotation.Autowired;
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.List;

/**
 * 获取用户的信息和角色信息
 */
@Service("myUserDetailsService")
public class MyUserDetailsService implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;
    @Autowired
    private RoleMapper roleMapper;
    @Autowired
    private UserRoleMapper userRoleMapper;

    @Override
    public UserDetails loadUserByUsername(String userName) {

        if(StringUtils.isNullOrEmpty(userName)){
            throw new UsernameNotFoundException("用户信息不能为空");
        }

        //获取用户信息
        UserExample userExample = new UserExample();
        UserExample.Criteria userCriteria = userExample.createCriteria();
        userCriteria.andUsernameEqualTo(userName);
        List<User> userList = userMapper.selectByExample(userExample);

        User user = null;
        if (userList.size() > 0) {
            user = userList.get(0);

            //获取用户与角色的关联信息
            UserRoleExample userRoleExample = new UserRoleExample();
            UserRoleExample.Criteria userRoleCriteria = userRoleExample.createCriteria();
            userRoleCriteria.andUserIdEqualTo(user.getId());
            List<UserRole> userRoleList = userRoleMapper.selectByExample(userRoleExample);

            if (userRoleList.size() > 0) {

                //获取用户的所有角色id
                List<Integer> roleIdList = new ArrayList<>();
                for (UserRole item : userRoleList) {
                    roleIdList.add(item.getRoleId());
                }

                //通过角色id获取角色信息
                RoleExample roleExample = new RoleExample();
                RoleExample.Criteria roleCriteria = roleExample.createCriteria();
                roleCriteria.andIdIn(roleIdList);
                List<Role> roleList = roleMapper.selectByExample(roleExample);

                //将角色信息赋值给用户
                user.setAuthorities(roleList);
            }
        }else {
            throw new UsernameNotFoundException("用户不存在");
        }

        return user;
    }
}

 

4.3、利用JavaConfig配置

SecurityAppInitializer
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;

/**
 * security的过滤器
 */
public class SecurityAppInitializer extends AbstractSecurityWebApplicationInitializer {
}
WebSecurityConfig(配置完成后,需要将其在2.4节的 WebAppInitializer 中添加)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.util.DigestUtils;

import javax.sql.DataSource;

@Configuration
@EnableWebSecurity//启用web安全性
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private DataSource dataSource;

    @Autowired
    @Qualifier("myUserDetailsService")
    private UserDetailsService userDetailsService;

    @Autowired
    private MyFilterSecurityInterceptor myFilterSecurityInterceptor;

    /**
     * 自定义校验用户方法
     *
     * @param auth
     * @throws Exception
     */
    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        /**
         * 方法一
         * 将userDetailsService添加,用于自定义的用户登录校验
         * 重写了密码加密后验证的方法(数据库中存储加密后的密码,但MD5加密已经不被SpringSecurity所推荐)
         */
        auth.userDetailsService(userDetailsService).passwordEncoder(new PasswordEncoder() {
            /**
             * 对密码进行加密,md5加密
             */
            @Override
            public String encode(CharSequence charSequence) {
                return DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());
            }

            /**
             * 对用户输入的密码进行匹配计算
             *
             * @param charSequence 用户输入的密码
             * @param s 数据库中的用户密码
             * @return
             */
            @Override
            public boolean matches(CharSequence charSequence, String s) {
                String encode = encode(charSequence);
                boolean res = s.equalsIgnoreCase(encode);
                return res;
            }
        });

        /**
         * 方法二:
         * 配置数据库连接,配置user-detail服务
         * 重写usersByUsernameQuery和authoritiesByUsernameQuery,自定义校验
         *  BCryptPasswordEncoder加密验证(推荐之一)
         *  NoOpPasswordEncoder.getInstance()数据库明码存储密码(不推荐)
         */
//        auth
//                .jdbcAuthentication().dataSource(dataSource)
//                .usersByUsernameQuery("select `name`, password, disable from users where `name` = ?")
//                .authoritiesByUsernameQuery("" +
//                        "select a.`name` , c.`name`\n" +
//                        "from users as a \n" +
//                        "inner join user_role as b on a.id = b.user_id\n" +
//                        "inner join roles as c on c.id = b.role_id\n" +
//                        "where a.`name` = ?")
//                .passwordEncoder(new BCryptPasswordEncoder());//BCrypt加密方法
                .passwordEncoder(NoOpPasswordEncoder.getInstance());//不加密
    }

    /**
     * 通过重载,配置SpringSecurity的Filter链
     *
     * @param web
     */
    @Override
    public void configure(WebSecurity web) {

        web.ignoring().antMatchers("/static/css/**");
        web.ignoring().antMatchers("/static/js/**");
        web.ignoring().antMatchers("/static/image/**");
        //忽略登录界面
        //web.ignoring().antMatchers("/login");

        //注册地址不拦截
//        web.ignoring().antMatchers("/reg");
    }

    /**
     * 配置如何通过拦截器保护请求
     * 
     * HttpSecurity用于提供一系列的Security默认的Filter,
     * 这个过程中:
     * 一是生成默认的FilterConfigurer对象并添加到其filters属性中存储 ;
     * 二是调用其performBuild方法生成DefaultSecurityFilterChain对象;
     * 在WebSecurityConfig中重载了WebSecurityConfigurerAdapter的configure方法,
     * 利用HttpSercurity对象提供的一些filter去实现我们自己的业务
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http
                .authorizeRequests().anyRequest().authenticated()
                .and()
                .formLogin().loginPage("/login").successForwardUrl("/main").failureUrl("/login-error").permitAll() // 登录
                .and()
                .logout().logoutSuccessUrl("/login") //登出
                .and()
                .exceptionHandling().accessDeniedPage("/401")// 权限不足,跳转401页面
                .and()
                .rememberMe().tokenValiditySeconds(1800)// 登录缓存时间
                // 将自定义的权限过滤器添加至过滤器FilterSecurityInterceptor的前面
                .and()
                .addFilterBefore(myFilterSecurityInterceptor, FilterSecurityInterceptor.class);
                //关闭csrf跨域攻击防护(开发时可关闭,但实际开发时不推荐)
                // 关闭csrf后,可以通过get或post请求,同时不需要携带_csrf的token       
                // 当开启时,需要post请求,同时需要hidden隐藏域中添加_csrf的token  
                //.and()
                //.csrf().disable();
    }
}
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected Class<?>[] getRootConfigClasses() {

        return new Class<?>[]{RootConfig.class, WebSecurityConfig.class};
    }

    //其他方法省略。。。
}

 

4.4、自定义拦截器 MyFilterSecurityInterceptor

实现 AbstractSecurityInterceptor 抽象类,实现 Filter 接口

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.SecurityMetadataSource;
import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
import org.springframework.security.access.intercept.InterceptorStatusToken;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import java.io.IOException;

/**
 * 自定义拦截器
 * 使用自定义的 MyAccessDecisionManager 和 MyFilterInvocationSecurityMetadataSource
 * 登陆后,每次访问资源都会被这个Interceptor(拦截器)拦截,会执行doFilter,调用了invoke方法
 */
@Component
public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {

    //注入自定义的验证资源提取类
    @Autowired
    @Qualifier("myFilterInvocationSecurityMetadataSource")
    private FilterInvocationSecurityMetadataSource securityMetadataSource;

        //提供获取自定义的验证资源获取类
    @Override
    public SecurityMetadataSource obtainSecurityMetadataSource() {
        return this.securityMetadataSource;
    }


    //注入自定义的校验方法
    @Autowired
    @Qualifier("myAccessDecisionManager")
    public void setMyAccessDecisionManager(AccessDecisionManager accessDecisionManager) {
        super.setAccessDecisionManager(accessDecisionManager);
    }

    //认证管理器,实现用户认证的入口,使用父类的
    @Override
    public void setAuthenticationManager(AuthenticationManager newManager) {
        super.setAuthenticationManager(newManager);
    }
    
    //在拦截器链中,会被执行,用于权限验证
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        FilterInvocation filterInvocation = new FilterInvocation(servletRequest, servletResponse, filterChain);
        invoke(filterInvocation);
    }

    public void invoke(FilterInvocation fi) throws IOException, ServletException{

        //beforeInvocation()方法实现了对访问受保护对象的权限校验,请求了MyAccessDecisionManager类的decide方法
        // 内部用到了AccessDecisionManager和AuthenticationManager;
        //如果请求者据有路径所需要的权限,则new了一个token对象--->InterceptorStatusToken
        InterceptorStatusToken token = super.beforeInvocation(fi);

        try {
            //执行下一个拦截器
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
        } finally {
            //方法实现了对返回结果的处理,在注入了AfterInvocationManager的情况下默认会调用其decide()方法
            //super:使用继承的抽象类AbstractSecurityInterceptor的afterInvocation方法
            super.afterInvocation(token, null);
        }
    }

    @Override
    public Class<?> getSecureObjectClass() {
        return FilterInvocation.class;
    }

}

4.5、自定义资源获取类 MyFilterInvocationSecurityMetadataSource

用于所有的受限资源访问所需要的角色,在自定义拦截器 MyFilterSecurityInterceptor 的 invoke方法的 super.beforeInvocation(fi) 中使用,调用抽象类 AbstractSecurityInterceptor 的 beforeInvocation() 方法执行下述行,来获取所有资源所需要的角色。

Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);
import com.zjst.tc.bean.RolePermissionDetail;
import com.zjst.tc.repository.mappers.RolePermissionMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import java.util.*;

/**
 * 从数据库中提取权限资源,供Spring security使用,用于权限校验
 */
@Component("myFilterInvocationSecurityMetadataSource")
public class MyFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {

    @Autowired
    private RolePermissionMapper rolePermissionMapper;

    //每一个资源所需要的角色 Collection<ConfigAttribute>决策器会用到
    //map的key存页面(资源)的url,value存所需要的角色(roles),可能有多个角色有权限访问该资源
    private static HashMap<String, Collection<ConfigAttribute>> map = null;

    /**
     * 返回请求的资源需要的角色
     *
     * @param o 对象包含 servletRequest, servletResponse, filterChain
     * @return
     * @throws IllegalArgumentException
     */
    @Override
    public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {

        //减少请求数据库的次数,防止二次锁
        if (map == null) {
            synchronized (MyFilterInvocationSecurityMetadataSource.class) {
                if (map == null) {
                    loadResourceDefine();
                }
            }
        }

        //object中包含 servletRequest, servletResponse, filterChain
        HttpServletRequest request = ((FilterInvocation) o).getHttpRequest();

        //将请求的url需要的角色返回
        for (Map.Entry<String, Collection<ConfigAttribute>> item : map.entrySet()) {
            if (new AntPathRequestMatcher(item.getKey()).matches(request)) {
                return map.get(item.getKey());
            }
        }

        return null;
    }

    /**
     * Spring容器启动时自动调用, 一般把所有请求与权限的对应关系也要在这个方法里初始化, 保存在一个属性变量里
     *
     * @return
     */
    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    /**
     * 该类是否能够为指定的方法调用或Web请求提供ConfigAttributes
     *
     * @param aClass
     * @return
     */
    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }
    
    /**
     * 初始化所有资源对应的角色
     */
    private void loadResourceDefine() {
        map = new HashMap<>();

        List<RolePermissionDetail> rolePermissionDetailList = rolePermissionMapper.selectAll();
        
        //某个资源 可以被哪些角色访问
        for (RolePermissionDetail item : rolePermissionDetailList) {

            String permissionUrl = item.getPermissionUrl();
            String roleName = item.getRoleName();
            ConfigAttribute role = new SecurityConfig(roleName);

            if (map.containsKey(permissionUrl)) {
                map.get(permissionUrl).add(role);
            } else {
                List<ConfigAttribute> ca = new ArrayList<>();
                ca.add(role);
                map.put(permissionUrl, ca);
            }

        }
    }
}

4.6、自定义决策器 MyAccessDecisionManager

自定义决策器会在 AbstractSecurityInterceptor 的 beforeInvocation 方法的下述行中被执行:

this.accessDecisionManager.decide(authenticated, object, attributes);

有权限则 return,否则抛出异常 AccessDeniedException

import org.mybatis.logging.Logger;
import org.mybatis.logging.LoggerFactory;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;

import java.util.Collection;


/**
 * 决策器
 * 鉴定用户是否有访问对应资源(方法或URL)的权限
 */
@Component("myAccessDecisionManager")
public class MyAccessDecisionManager implements AccessDecisionManager {

    //目前没有使用,可以将权限验证情况写入日志中
    private final static Logger logger = LoggerFactory.getLogger(MyAccessDecisionManager.class);

    /**
     * 通过传递的参数来决定用户是否有访问对应受保护对象的权限
     *
     * @param authentication 包含了当前的用户信息,包括拥有的权限。
     *                       这里的权限来源就是前面登录时UserDetailsService中设置的authorities。
     *                       SecurityContextHolder.getContext().getAuthentication();
     * @param o              就是FilterInvocation对象,包含本次请求的 servletRequest, servletResponse, filterChain
     * @param collection     本次访问需要的权限(角色list,存的是角色name)
     */
    @Override
    public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {

        //不需要权限就可以访问
        if (collection == null || collection.size() <= 0) {
            return;
        }

        for (ConfigAttribute item : collection) {
            //只需要请求者所据有的角色中,包含路径所需要的角色(权限)
            //item.getAttribute()装的是请求路径所需要的roleName
            //authentication.getAuthorities()请求者所据有的roles
            if (authentication.getAuthorities().size() > 0) {
                for (GrantedAuthority item2 : authentication.getAuthorities()) {
                    if (item2.getAuthority().contains(item.getAttribute())) {
                        return;
                    }
                }
            }
        }
        throw new AccessDeniedException("无权限访问");
    }

    /**
     * 表示此AccessDecisionManager是否能够处理传递的ConfigAttribute呈现的授权请求
     *
     * @param configAttribute
     * @return
     */
    @Override
    public boolean supports(ConfigAttribute configAttribute) {
        return true;
    }

    /**
     * 表示当前AccessDecisionManager是否能够为指定的安全对象(方法调用或Web请求)提供访问控制决策
     *
     * @param aClass
     * @return
     */
    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }
}

 

4.7、请求 SecurityController

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class SecurityController {

    @RequestMapping({"/", "/index"})
    public String root() {
        return "redirect:/login";
    }

    //列表页面
    @RequestMapping(value = "/main", method = RequestMethod.POST)
    public String main() {
        return "main";
    }

    @RequestMapping("/login")
    public String login() {
        //SpringSecurity框架进行校验
        //通过后跳转至首页
        return "login";
    }

    //若登录失败(用户名或密码不正确)
    @RequestMapping("/login-error")
    public String loginError(Model model) {
        
        //model用于在login页面显示提示
        model.addAttribute("loginError", true);
        return "login";
    }

    //用于无权限访问提示页面
    @GetMapping("/401")
    public String accessDenied() {
        return "user/401";
    }

    @GetMapping("/user/common")
    //@PreAuthorize("hasRole('ROLE_COMMON')")
    public String common() {
        return "user/common";
    }

    @GetMapping("/user/admin")
    //@PreAuthorize("hasRole('ROLE_ADMIN')")
    public String admin() {
        return "user/admin";
    }


}

4.8、JSP

login.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="sf" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
<h1>Login page</h1>

<c:if test="${loginError}">
    <span style="color:red">提示:用户名或密码错误</span>
</c:if>

<sf:form action="/login" method="post">
    <label for="username">用户名</label>:
    <input type="text" id="username" name="username" autofocus="autofocus" />
    <br/>
    <label for="password">密 码</label>:
    <input type="password" id="password" name="password" />
    <br/>
    <input type="submit" value="登录" />
</sf:form>
</body>
</html>

main.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %>
<%@ taglib prefix="sf" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <head>
        <meta charset="UTF-8">
        <title>首页</title>
    </head>
<body>
<h2>page list</h2>
<a href="/user/common">common page</a>
<br/>
<a href="/user/admin">admin page</a>
<br/>

<br/>
需要将csrf的token利用post请求提交至后台,如果关闭csrf,则post和get均可
<form action="${pageContext.request.contextPath }/logout" method="post">
    <input type="hidden" name="${_csrf.parameterName }" value="${_csrf.token }"/>
    <input type="submit" value="logout">
</form>

<br/>
使用spring-form标签,可以自动添加_csrf的token
<sf:form action="/logout" method="post">
    <input type="submit" value="logout">
</sf:form>

</body>
</html>

401.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h1>this is 401 page</h1>
<div>
    <span>Insufficient permissions, access denied</span><br/>
    <span>权限不足, 拒绝访问</span>
</div>
<br/>
为了防止csrf的跨域攻击,请求带token
<form action="/main" method="post">
    <input type="hidden" name="${_csrf.parameterName }" value="${_csrf.token }"/>
    <input type="submit" value="main">
</form>
</body>
</html>

admin.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h1>this is admin page</h1>
<br/>
为了防止csrf的跨域攻击,请求带token
<form action="/main" method="post">
    <input type="hidden" name="${_csrf.parameterName }" value="${_csrf.token }"/>
    <input type="submit" value="main返回列表页">
</form>
</body>
</html>

common.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h1>this is common page</h1>
<br/>
为了防止csrf的跨域攻击,请求带token
<form action="/main" method="post">
    <input type="hidden" name="${_csrf.parameterName }" value="${_csrf.token }"/>
    <input type="submit" value="main返回列表页">
</form>
</body>
</html>

5、最后贴上整个项目的构成图

6、参考文献(博文)

spring、springmvc和mybatis整合(java config方式)

https://www.cnblogs.com/hhhshct/p/9688079.html

----------------------------------------------------------------------------------------------

Spring Boot Security 详解

https://www.jianshu.com/p/e715cc993bf0

springMvc+spring security 注解方式实现权限控制

https://blog.csdn.net/camellia919/article/details/81945806

基于Spring Security实现权限管理系统

https://blog.csdn.net/cloume/article/details/83790111

Spring Security用户认证和权限控制(自定义实现)

https://blog.csdn.net/weixin_44516305/article/details/88868791

----------------------------------------------------------------------------------------------

SpringSecurity默认访问授权流程图

https://blog.csdn.net/weixin_33869377/article/details/92131050

SpringSecurity禁用匿名用户(anonymous().disable())后无限重定向到登录页的问题解决

http://blog.joylau.cn/2019/08/19/SpringBoot-SpringSecurity-Anonymous/

----------------------------------------------------------------------------------------------

SpringSecurity前后端分离下对登录认证的管理

https://blog.csdn.net/XlxfyzsFdblj/article/details/82083443

SpringSecurity登录验证步骤和源码浅析

https://blog.csdn.net/XlxfyzsFdblj/article/details/82084183#commentsedit

SpringSecurity自定义权限校验和源码浅析

https://blog.csdn.net/XlxfyzsFdblj/article/details/82262469

----------------------------------------------------------------------------------------------

SpringSecurity自定义异常处理

https://blog.csdn.net/XlxfyzsFdblj/article/details/82290500

----------------------------------------------------------------------------------------------

Spring Security 默认的过滤器链

https://blog.csdn.net/shenchaohao12321/article/details/87355803

spring security 标准Filter及其在filter chain的顺序

https://blog.csdn.net/shierqu/article/details/49538791

https://blog.csdn.net/selfsojourner/article/details/70891385

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值