赞
踩
Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。由于它是Spring生态系统中的一员,因此它伴随着整个Spring生态系统不断修正、升级,在spring boot项目中加入springsecurity更是十分简单,使用Spring Security 减少了为企业系统安全控制编写大量重复代码的工作。
<dependencies> <!-- 基于springboot的springsecurity依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- springboot依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 以下是jsp依赖--> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> <!-- jsp页面使用jstl标签 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> </dependency> <!-- 用于编译jsp--> <!-- <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> <version>9.0.36</version> <scope>provided</scope> </dependency>--> <!--基于lombok依赖--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build>
server:
port: 8080
servlet:
context-path: /security-springboot
spring:
application:
name: security-springboot
mvc:
view:
prefix: /WEB-INF/views
suffix: .jsp
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("redirect:/login");
}
}
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.provisioning.InMemoryUserDetailsManager; @Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter { //配置用户信息服务 @Bean @Override public UserDetailsService userDetailsService(){ //实际开发中该处读取数据库中的用户数据 此处为构造的测试数据 InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build()); manager.createUser(User.withUsername("lisi").password("456").authorities("p2").build()); return manager; } @Bean public PasswordEncoder passwordEncoder(){ //采用不加密的形式比较密码 return NoOpPasswordEncoder.getInstance(); } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers("/r/r1").hasAuthority("p1") //web路径权限添加 .antMatchers("/r/r2").hasAuthority("p2") .antMatchers("/r/**").authenticated() .anyRequest().permitAll() .and() .formLogin().successForwardUrl("/loginsuccess").permitAll(); //允许表单登录 登录成功后调转到该路径 }
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class LoginController { @RequestMapping(value = "/loginsuccess") public String loginSuccess(){ return " 登录成功"; } /** * 测试资源1 * @return */ @GetMapping(value = "/r/r1") public String r1(){ return " 访问资源1"; } /** * 测试资源2 * @return */ @GetMapping(value = "/r/r2") public String r2(){ return " 访问资源2"; } }
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringsecurityApplication {
public static void main(String[] args) {
SpringApplication.run(SpringsecurityApplication.class, args);
}
}
登录认证
<properties> <java.version>1.8</java.version> <servlet.version>4.0.1</servlet.version> <freemarker.version>2.3.28</freemarker.version> <fastjson.version>1.2.47</fastjson.version> <mybatis-plus.version>3.2.0</mybatis-plus.version> <mybatis-plus-generator.version>3.2.0</mybatis-plus-generator.version> <velocity.version>1.7</velocity.version> <pagehelper.version>4.1.6</pagehelper.version> <springfox-swagger2.version>2.9.2</springfox-swagger2.version> <swagger-annotations.version>1.5.21</swagger-annotations.version> <springfox-swagger-ui.version>2.9.2</springfox-swagger-ui.version> </properties> <dependencies> <!-- 基于springboot的springsecurity依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- springboot依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 以下是jsp依赖--> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>${servlet.version}</version> <scope>provided</scope> </dependency> <!-- jsp页面使用jstl标签 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> </dependency> <!--基于lombok依赖--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> <!--整合mybatis-plus相关依赖--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>${mybatis-plus.version}</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>${mybatis-plus-generator.version}</version> </dependency> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>${freemarker.version}</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>${fastjson.version}</version> </dependency> <!-- velocity 模板引擎, 使用代码生成器时添加 --> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity</artifactId> <version>${velocity.version}</version> </dependency> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>${pagehelper.version}</version> </dependency> <!-- swagger --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>${springfox-swagger2.version}</version> <exclusions> <exclusion> <groupId>io.swagger</groupId> <artifactId>swagger-annotations</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>io.swagger</groupId> <artifactId>swagger-annotations</artifactId> <version>${swagger-annotations.version}</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>${springfox-swagger-ui.version}</version> </dependency> </dependencies>
server: port: 8281 servlet: context-path: /security-springboot spring: application: name: security-springboot #视图解析 mvc: view: prefix: /WEB-INF/views suffix: .jsp #数据源配置 datasource: driver-class-name: com.mysql.cj.jdbc.Driver username: root password: 123456 url: jdbc:mysql://127.0.0.1:3306/test?serverTimezone=GMT%2B8&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false initialSize: 2 minIdle: 2 maxActive: 20 maxWait: 60000 timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 validationQuery: SELECT 1 FROM DUAL testWhileIdle: true testOnBorrow: false testOnReturn: false #mybatis-plus相关配置 mybatis-plus: configuration: map-underscore-to-camel-case: true auto-mapping-behavior: full log-impl: org.apache.ibatis.logging.stdout.StdOutImpl mapper-locations: classpath*:mapper/**/*Mapper.xml # 逻辑删除配置 global-config: db-config: logic-delete-value: 1 logic-not-delete-value: 0
package generator; import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException; import com.baomidou.mybatisplus.core.toolkit.StringPool; import com.baomidou.mybatisplus.generator.AutoGenerator; import com.baomidou.mybatisplus.generator.InjectionConfig; import com.baomidou.mybatisplus.generator.config.*; import com.baomidou.mybatisplus.generator.config.po.TableInfo; import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine; import org.apache.commons.lang.StringUtils; import java.util.ArrayList; import java.util.List; import java.util.Scanner; /** * 生成启动类 */ public class StartGenerator { /** * 数据库url */ public static String url = "jdbc:mysql://127.0.0.1:3306/test?serverTimezone=GMT%2B8&characterEncoding=utf8&zeroDateTimeBehavior" + "=convertToNull"; /** * 用户名 */ public static String userName = "root"; /** * 密码 */ public static String userPwd = "123456"; /** * 启动函数 * * @param args */ public static void main(String[] args) { // 代码生成器 AutoGenerator mpg = new AutoGenerator(); // 全局配置 GlobalConfig gc = new GlobalConfig(); String projectPath = System.getProperty("user.dir"); // 文件输出路径 gc.setOutputDir(projectPath + "/src/main/java"); // 作者 gc.setAuthor("immortal"); gc.setOpen(false); gc.setSwagger2(true); gc.setBaseResultMap(true); gc.setBaseColumnList(true); // 不覆盖已有,则为false gc.setFileOverride(false); mpg.setGlobalConfig(gc); // 数据源配置 DataSourceConfig dsc = new DataSourceConfig(); dsc.setUrl(StartGenerator.url); dsc.setDriverName("com.mysql.jdbc.Driver"); dsc.setUsername(StartGenerator.userName); dsc.setPassword(StartGenerator.userPwd); mpg.setDataSource(dsc); // 包配置 PackageConfig pc = new PackageConfig(); pc.setModuleName("xmlMapper"); pc.setParent("com.immortal"); mpg.setPackageInfo(pc); // 自定义配置 InjectionConfig cfg = new InjectionConfig() { @Override public void initMap() { // to do nothing } }; List<FileOutConfig> focList = new ArrayList<>(); focList.add(new FileOutConfig("/templates/mapper.xml.ftl") { @Override public String outputFile(TableInfo tableInfo) { // 自定义输入文件路径和名称 return projectPath + "/src/main/java/com/immortal/springsecurity/mapper/userMapper" + pc.getModuleName() + "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML; } }); cfg.setFileOutConfigList(focList); mpg.setCfg(cfg); mpg.setTemplate(new TemplateConfig().setXml(null)); // 策略配置 StrategyConfig strategy = new StrategyConfig(); // 表名生成策略 strategy.setNaming(NamingStrategy.underline_to_camel); strategy.setColumnNaming(NamingStrategy.underline_to_camel); strategy.setSuperEntityClass(""); strategy.setEntityLombokModel(true); strategy.setRestControllerStyle(true); strategy.setSuperControllerClass(null); // 多个表名传数组 strategy.setInclude(tableArray()); strategy.setSuperEntityColumns("id"); strategy.setControllerMappingHyphenStyle(true); strategy.setTablePrefix(pc.getModuleName() + "_"); strategy.setVersionFieldName("version"); strategy.setLogicDeleteFieldName("deleted"); strategy.setEntityColumnConstant(true); mpg.setStrategy(strategy); mpg.setTemplateEngine(new FreemarkerTemplateEngine()); mpg.execute(); } /** * <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 (StringUtils.isNotEmpty(ipt)) { return ipt; } } throw new MybatisPlusException("请输入正确的" + tip + "!"); } /** * 生成的表名 * @return */ public static String[] tableArray() { try { List<String> tableList = new ArrayList<>(16); // 多表生成 tableList.add("t_user"); tableList.add("t_role"); tableList.add("t_permission"); return tableList.toArray(new String[tableList.size()]); } catch (Exception e) { throw new MybatisPlusException("请输入正确的表名!"); } } }
entity.java.ftl文件:
package ${package.Entity}; <#list table.importPackages as pkg> import ${pkg}; </#list> <#if swagger2> import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; </#if> <#if entityLombokModel> import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors; </#if> import java.util.Date; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; /** * * ${table.comment!} * * @author ${author} * @since ${date} */ <#if entityLombokModel> @Data <#if superEntityClass??> @EqualsAndHashCode(callSuper = true) <#else> @EqualsAndHashCode(callSuper = false) </#if> @Accessors(chain = true) </#if> <#if table.convert> @TableName("${table.name}") </#if> <#if swagger2> @ApiModel(value="${entity}对象", description="${table.comment!}") </#if> <#if superEntityClass??> public class ${entity} extends ${superEntityClass}<#if activeRecord><${entity}></#if> { <#elseif activeRecord> public class ${entity} extends Model<${entity}> { <#else> public class ${entity} implements Serializable { </#if> private static final long serialVersionUID = 1L; <#-- ---------- BEGIN 字段循环遍历 ----------> <#list table.fields as field> <#if field.keyFlag> <#assign keyPropertyName="${field.propertyName}"/> </#if> <#if field.comment!?length gt 0> <#if swagger2> @ApiModelProperty(value = "${field.comment}") <#else> /** * ${field.comment} */ </#if> </#if> <#if field.keyFlag> @TableId(value="${field.name}", type = IdType.INPUT) <#-- 普通字段 --> <#elseif field.fill??> <#-- ----- 存在字段填充设置 -----> <#if field.convert> @TableField(value = "${field.name}", fill = FieldFill.${field.fill}) <#else> @TableField(fill = FieldFill.${field.fill}) </#if> <#elseif field.convert> @TableField("${field.name}") </#if> <#-- 乐观锁注解 --> <#if (versionFieldName!"") == field.name> @Version </#if> <#-- 逻辑删除注解 --> <#if (logicDeleteFieldName!"") == field.name> @TableLogic </#if> private ${(field.propertyType == 'LocalDateTime')?string('Date',field.propertyType)} ${field.propertyName}; </#list> <#------------ END 字段循环遍历 ----------> <#if !entityLombokModel> <#list table.fields as field> <#if field.propertyType == "boolean"> <#assign getprefix="is"/> <#else> <#assign getprefix="get"/> </#if> public ${field.propertyType} ${getprefix}${field.capitalName}() { return ${field.propertyName}; } <#if entityBuilderModel> public ${entity} set${field.capitalName}(${field.propertyType} ${field.propertyName}) { <#else> public void set${field.capitalName}(${field.propertyType} ${field.propertyName}) { </#if> this.${field.propertyName} = ${field.propertyName}; <#if entityBuilderModel> return this; </#if> } </#list> </#if> <#if entityColumnConstant> <#list table.fields as field> public static final String ${field.name?upper_case} = "${field.name}"; </#list> </#if> <#if activeRecord> @Override protected Serializable pkVal() { <#if keyPropertyName??> return this.${keyPropertyName}; <#else> return null; </#if> } </#if> <#if !entityLombokModel> @Override public String toString() { return "${entity}{" + <#list table.fields as field> <#if field_index==0> "${field.propertyName}=" + ${field.propertyName} + <#else> ", ${field.propertyName}=" + ${field.propertyName} + </#if> </#list> "}"; } </#if> }
mapper.xml.ftl文件:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="${package.Mapper}.${table.mapperName}"> <#if enableCache> <!-- 开启二级缓存 --> <cache type="org.mybatis.caches.ehcache.LoggingEhcache"/> </#if> <#if baseResultMap> <!-- 通用查询映射结果 --> <resultMap id="BaseResultMap" type="${package.Entity}.${entity}"> <#list table.fields as field> <#if field.keyFlag><#--生成主键排在第一位--> <id column="${field.name}" property="${field.propertyName}"/> </#if> </#list> <#list table.commonFields as field><#--生成公共字段 --> <result column="${field.name}" property="${field.propertyName}"/> </#list> <#list table.fields as field> <#if !field.keyFlag><#--生成普通字段 --> <result column="${field.name}" property="${field.propertyName}"/> </#if> </#list> </resultMap> </#if> <#if baseColumnList> <!-- 通用查询结果列 --> <sql id="Base_Column_List"> <#list table.commonFields as field> ${field.name}, </#list> ${table.fieldNames} </sql> </#if> </mapper>
用户表
用户-角色表
角色权限表
角色表
角色-权限表
表结构描述:张三是角色1,角色1拥有p1和p3权限,能访问r/r1和/r/r3路径;李四是角色2,角色2拥有p2权限,能访问/r/r2路径。
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.immortal.springsecurity.entity.TPermission; import com.immortal.springsecurity.entity.TUser; import com.immortal.springsecurity.mapper.userMapper.UserMapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import javax.annotation.Resource; import java.util.ArrayList; import java.util.List; @Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter implements UserDetailsService { @Resource private UserMapper userMapper; /** * 从数据库查询用户信息与客户端输入的进行比较 * @param username * @return * @throws UsernameNotFoundException */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { QueryWrapper<TUser> userQueryWrapper = new QueryWrapper<>(); userQueryWrapper.eq(TUser.USERNAME,username); TUser user = userMapper.selectOne(userQueryWrapper); List<TPermission> authorityByUserId = userMapper.getAuthorityByUserId(user.getId()); List<String> authoritys = new ArrayList<>(); authorityByUserId.iterator().forEachRemaining(c -> authoritys.add(c.getCode())); String[] auths = new String[authoritys.size()]; authoritys.toArray(auths); if(user != null){ UserDetails userDetails = org.springframework.security.core.userdetails.User .withUsername(user.getUsername()) .password(user.getPassword()) .authorities(auths) .build(); return userDetails; } return null; } /** * 密码的加密方式 * @return */ @Bean public PasswordEncoder passwordEncoder(){ //采用不加密的形式比较密码 return NoOpPasswordEncoder.getInstance(); } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers("/r/r1").hasAuthority("p1") //web路径权限添加 .antMatchers("/r/r2").hasAuthority("p2") .antMatchers("/r/r3").hasAuthority("p3") .antMatchers("/r/**").authenticated() .anyRequest().permitAll() .and() .formLogin().successForwardUrl("/loginsuccess"); //允许表单登录 登录成功后调转到该路径 } }
张三登录成功并能访问/r/r1和/r/r3路径;李四登录成功只能访问/r/r2;为了方便测试,此处密码的比对还是采用不加密方式。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。