一、简介

SpringSecurity是一个功能强大且高度可定制的身份验证和访问控制框架,和spring项目整合更加方便。

二、核心功能

  • 认证(Authentication):指的是验证某个用户能否访问该系统。
  • 授权(Authorization):指的是验证某个用户是否有权限执行某个操作。

三、搭建v1.0版本

1、新建一个springboot项目

myspringsecurity

2、添加maven依赖

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

3、新建一个test的controller

package com.zb.myspringsecurity.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; @RestController
@RequestMapping("/demo")
public class DemoController { @RequestMapping("/hello")
public String hello() {
return "hello world";
}
}

4、启动项目

MyspringsecurityApplication.main();

5、用浏览器测试

http://localhost:8080/demo/hello

我们会发现浏览器会跳转到login页面,如下图

6、密码登陆

我们可以在项目启动日志里面找到密码

2021-07-19 10:58:48.558  INFO 5244 --- [           main] .s.DelegatingFilterProxyRegistrationBean : Mapping filter: 'springSecurityFilterChain' to: [/*]
2021-07-19 10:58:48.558 INFO 5244 --- [ main] o.s.b.w.servlet.ServletRegistrationBean : Servlet dispatcherServlet mapped to [/]
2021-07-19 10:58:48.684 INFO 5244 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2021-07-19 10:58:48.812 INFO 5244 --- [ main] .s.s.UserDetailsServiceAutoConfiguration : Using generated security password: ced4127a-1677-438e-a65b-2ab219137083 2021-07-19 10:58:48.868 INFO 5244 --- [ main] o.s.s.web.DefaultSecurityFilterChain : Creating filter chain: any request, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@40021799, org.springframework.security.web.context.SecurityContextPersistenceFilter@2d7e1102, org.springframework.security.web.header.HeaderWriterFilter@3fbfa96, org.springframework.security.web.csrf.CsrfFilter@61533ae, org.springframework.security.web.authentication.logout.LogoutFilter@4a699efa, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@4482469c, org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@4917d36b, org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@4a1c0752, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@278f8425, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@2adddc06, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@4ebadd3d, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@332f25c8, org.springframework.security.web.session.SessionManagementFilter@466d49f0, org.springframework.security.web.access.ExceptionTranslationFilter@599f571f, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@7004e3d]
2021-07-19 10:58:48.911 INFO 5244 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2021-07-19 10:58:48.914 INFO 5244 --- [ main] c.z.m.MyspringsecurityApplication : Started MyspringsecurityApplication in 1.458 seconds (JVM running for 2.405)
  • 用户名是:user
  • 密码(从日志找到)是:ced4127a-1677-438e-a65b-2ab219137083

登录成功如下图:

二、进阶版v2.0(配置文件配置用户密码)

1、配置文件里面写用户名密码

刚才的密码生成在日志里面了,实际使用很不方便,可以把密码用户名固定配置一下

spring.security.user.name=admin
spring.security.user.password=123
  • 重新启动项目,会发现没有生成密码的日志了
  • 测试用新的用户名密码没问题

三、v3.0(java类里面写用户名密码)

1、在java类里面配置用户名密码

刚才是写在配置文件里面,我们还可以写到java类里面

package com.zb.myspringsecurity.config.security;

import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder; @EnableWebSecurity
public class ZbWebSecurityConfigurer extends WebSecurityConfigurerAdapter { @Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
} public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("zhangsan")
.password(passwordEncoder().encode("123"))
.roles("ADMIN")
.and()
.withUser("lisi")
.password(passwordEncoder().encode("123"))
.roles("ADMIN")
.and()
.withUser("wangwu")
.password(passwordEncoder().encode("123"))
.roles("ADMIN")
;
} }

我们再重启项目测试一下,发现用三个用户名密码都没问题。

四、v4.0(ignore url)

1、修改上面的java类,增加两个方法

@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(
//"/**/*.html",
"/**/*.js",
"/**/*.css",
"/**/*.ico",
"/**/*.jpg",
"/**/*.png",
"/test/**" // 忽略test
);
} @Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.and()
.httpBasic(); }
  • 我们配置了ignore的url
  • 我们再加一个controller用来测试
package com.zb.myspringsecurity.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; @RestController
@RequestMapping("/test")
public class TestController { @RequestMapping("/test")
public String hello() {
return "hello test";
}
}
  • 注意我们上面的ignore里面有:"/test/**"
  • 也就是说TestController 不会有登录校验

重启项目测试一下没问题

五、v5.0(在数据库里面配置用户名密码)

1、新建mysql库


create database myspringsecurity CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; create user securityuser IDENTIFIED by 'securitypass'; grant all privileges on myspringsecurity.* to securityuser@localhost identified by 'securitypass'; flush privileges;

2、新建用户表和角色表

DROP TABLE IF EXISTS `tb_user`;

CREATE TABLE `tb_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_name` varchar(50) DEFAULT NULL,
`password` varchar(100) DEFAULT NULL,
`mobile` int(11) DEFAULT NULL,
`sex` int(2) DEFAULT NULL,
`email` varchar(50) DEFAULT NULL,
`status` int(2) DEFAULT NULL,
`create_time` DATE DEFAULT NULL,
`create_id` int(11) DEFAULT NULL,
`update_time` date DEFAULT NULL,
`update_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `tb_role`; CREATE TABLE `tb_role` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`role_name` varchar(50) DEFAULT NULL,
`status` int(2) DEFAULT NULL,
`create_time` DATE DEFAULT NULL,
`create_id` int(11) DEFAULT NULL,
`update_time` date DEFAULT NULL,
`update_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `tr_user_role`; CREATE TABLE `tr_user_role` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) DEFAULT NULL,
`role_id` bigint(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

3、初始化一些数据

insert into `tb_user` (id, user_name, password) values (1, 'zhangsan', '123');
insert into `tb_user` (id, user_name, password) values (2, 'lisi', '123');
insert into `tb_user` (id, user_name, password) values (3, 'wangwu', '123'); insert into `tb_role` (id, role_name) values (1, '系统管理员');
insert into `tb_role` (id, role_name) values (2, '一般操作员'); insert into `tr_user_role` (id, user_id, role_id) values (1, 1, 1);
insert into `tr_user_role` (id, user_id, role_id) values (2, 1, 2);
insert into `tr_user_role` (id, user_id, role_id) values (3, 2, 2);

4、加入maven依赖

我们这里用了mybatis-plus。

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.44</version>
</dependency> <dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.2</version>
</dependency>

5、引入mybatis-plus自动生成代码的依赖

<!-- mybatis-plus代码生成 -->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.29</version>
</dependency> <dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.1.2</version>
</dependency>

6、修改代码生成类

package com.zb.myspringsecurity.config.mybatis;

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 java.util.ArrayList;
import java.util.List; public class MybatisGenerator { public static void main(String[] args) {
AutoGenerator mpg = new AutoGenerator(); // 全局配置
GlobalConfig gc = new GlobalConfig();
final String projectPath = System.getProperty("user.dir");
gc.setOutputDir(projectPath + "/src/main/java");
gc.setAuthor("system");
gc.setOpen(false);
gc.setFileOverride(true);
gc.setBaseResultMap(true);
// gc.setSwagger2(true); 实体属性 Swagger2 注解
mpg.setGlobalConfig(gc); // 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://127.0.0.1:3306/myspringsecurity?useUnicode=true&useSSL=false&characterEncoding=utf8");
// dsc.setSchemaName("public");
dsc.setDriverName("com.mysql.jdbc.Driver");
dsc.setUsername("securityuser");
dsc.setPassword("securitypass");
mpg.setDataSource(dsc); // 包配置
PackageConfig pc = new PackageConfig();
pc.setParent("com.zb.myspringsecurity");
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<>();
// 自定义配置会被优先输出
focList.add(new FileOutConfig(templatePath) {
@Override
public String outputFile(TableInfo tableInfo) {
// 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
return projectPath + "/src/main/resources/mapper/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
}
});
cfg.setFileOutConfigList(focList);
mpg.setCfg(cfg); // 配置模板
TemplateConfig templateConfig = new TemplateConfig();
templateConfig.setXml(null);
mpg.setTemplate(templateConfig); // 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
// strategy.setSuperEntityClass("com.baomidou.ant.common.BaseEntity");
strategy.setEntityLombokModel(true);
strategy.setRestControllerStyle(false);
// 公共父类
// strategy.setSuperControllerClass("com.baomidou.ant.common.BaseController");
// 写于父类中的公共字段
// strategy.setSuperEntityColumns("id");
strategy.setInclude("tb_user","tb_role","tr_user_role");
// strategy.setControllerMappingHyphenStyle(true);
strategy.setTablePrefix("tb_", "tr_");
mpg.setStrategy(strategy);
mpg.setTemplateEngine(new FreemarkerTemplateEngine());
mpg.execute();
}
}
  • 执行生成mapper,service和controller
  • mybatis准备好了,可以修改security配置了

7、修改configure方法

原先的:

public void configure(AuthenticationManagerBuilder auth) throws Exception {

    auth.inMemoryAuthentication()
.withUser("zhangsan")
.password(passwordEncoder().encode("123"))
.roles("ADMIN")
.and()
.withUser("lisi")
.password(passwordEncoder().encode("123"))
.roles("ADMIN")
.and()
.withUser(passwordEncoder().encode("123"))
.password("123")
.roles("ADMIN")
; }

改成新的:

@Autowired
ZxUserDetailsServiceImpl zxUserDetailsService; public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(zxUserDetailsService);
}

8、新增ZxUserDetailsServiceImpl类

package com.zb.myspringsecurity.config.security;

import com.zb.myspringsecurity.entity.Role;
import com.zb.myspringsecurity.entity.User;
import com.zb.myspringsecurity.entity.UserRole;
import com.zb.myspringsecurity.service.IUserRoleService;
import com.zb.myspringsecurity.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.PasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils; import java.util.ArrayList;
import java.util.List; @Component
public class ZxUserDetailsServiceImpl implements UserDetailsService { @Autowired
PasswordEncoder passwordEncoder;
@Autowired
IUserService iUserService;
@Autowired
IUserRoleService iUserRoleService; @Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
/**
// DEMO: List<SimpleGrantedAuthority> authorityList = new ArrayList<>();
authorityList.add(new SimpleGrantedAuthority("Admin")); ZxUser zxUser = new ZxUser();
zxUser.setUserName("zhangsanfeng");
zxUser.setPassword(passwordEncoder.encode("123"));
zxUser.setAuthorities(authorityList);
return zxUser; */ List<User> userList = iUserService.lambdaQuery().eq(User::getUserName, username).list();
if (CollectionUtils.isEmpty(userList)) {
throw new UsernameNotFoundException("不存在的用户");
}
User user = userList.get(0);
ZxUser zxUser = new ZxUser();
zxUser.setUserName(user.getUserName());
zxUser.setPassword(passwordEncoder.encode(user.getPassword()));
zxUser.setId(user.getId()); List<UserRole> userRoleList = iUserRoleService.lambdaQuery().eq(UserRole::getUserId, zxUser.getId()).list();
List<SimpleGrantedAuthority> authorityList = new ArrayList<>();
if (!CollectionUtils.isEmpty(userRoleList)) {
for (UserRole userRole : userRoleList) {
authorityList.add(new SimpleGrantedAuthority(String.valueOf(userRole.getRoleId())));
}
}
zxUser.setAuthorities(authorityList);
return zxUser;
}
}

9、新增自定义user

package com.zb.myspringsecurity.config.security;

import com.zb.myspringsecurity.entity.User;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection; @Data
public class ZxUser extends User implements UserDetails { private Collection<? extends GrantedAuthority> authorities; @Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
} @Override
public String getPassword() {
return super.getPassword();
} @Override
public String getUsername() {
return super.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;
}
}

10、验证

重启服务,用数据库里面的用户和密码验证没问题。

六、v6.0(实现前后端分离,token校验)

1、引入认证管理器 bean

/**
* 认证管理器
* @return
* @throws Exception
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}

2、写一个统一的登录接口

package com.zb.myspringsecurity.controller;

import com.zb.myspringsecurity.config.security.ZxUser;
import com.zb.myspringsecurity.config.security.ZxUserDetailsServiceImpl;
import com.zb.myspringsecurity.config.vo.CommonResponse;
import com.zb.myspringsecurity.config.vo.LoginParamVo;
import com.zb.myspringsecurity.config.vo.TokenVo;
import com.zb.myspringsecurity.service.TokenService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; @Slf4j
@RestController
@RequestMapping("/login")
public class LoginController { @Autowired
private AuthenticationManager authenticationManager; @Resource
ZxUserDetailsServiceImpl userDetailsService; @Resource
TokenService tokenService; @RequestMapping("/login-in")
public CommonResponse<TokenVo> login(@RequestBody LoginParamVo loginParamVo) {
try {
// 1 创建UsernamePasswordAuthenticationToken
UsernamePasswordAuthenticationToken token
= new UsernamePasswordAuthenticationToken(loginParamVo.getUsername(), loginParamVo.getPassword());
// 2 认证
Authentication authentication = this.authenticationManager.authenticate(token);
// 3 保存认证信息
SecurityContextHolder.getContext().setAuthentication(authentication);
// 4 加载UserDetails
ZxUser zxUser = this.userDetailsService.loadUserByUsername(loginParamVo.getUsername());
// 5 生成自定义token
TokenVo tokenVo = tokenService.createToken(zxUser);
return CommonResponse.successWithData(tokenVo);
} catch (Exception e) {
return CommonResponse.fail(401, e.getMessage());
} }
}

3、tokenservice

package com.zb.myspringsecurity.service;

import com.zb.myspringsecurity.config.vo.TokenVo;
import org.springframework.security.core.userdetails.UserDetails; public interface TokenService { TokenVo createToken(UserDetails details); boolean verifyToken(String token); String getUserNameByToken(String token);
}

简单的实现:

package com.zb.myspringsecurity.service.impl;

import com.zb.myspringsecurity.config.vo.TokenVo;
import com.zb.myspringsecurity.service.TokenService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service; import java.util.HashMap;
import java.util.Map;
import java.util.UUID; @Service
public class TokenServiceImpl implements TokenService { // todo 可以存redis, 设置过期时间
private static final Map<String, String> tokenMap = new HashMap<>(); @Override
public TokenVo createToken(UserDetails details) {
String token = UUID.randomUUID().toString();
tokenMap.put(token, details.getUsername()); TokenVo tokenVo = new TokenVo();
tokenVo.setToken(token);
tokenVo.setExpireTime(60*60); return tokenVo;
} @Override
public boolean verifyToken(String token) {
return tokenMap.get(token) != null;
} @Override
public String getUserNameByToken(String token) {
return tokenMap.get(token);
} }
  • 创建token, 校验token, 根据token获取username
  • 存的是token和username的关系

4、修改校验方式

修改为:SessionCreationPolicy.STATELESS

@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.and()
.httpBasic()
; }

5、增加一个校验token的filter

@Autowired
ZbTokenAuthenticationFilter zbTokenAuthenticationFilter; httpSecurity.addFilterBefore(zbTokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

filter:

package com.zb.myspringsecurity.config.security.customer;

import com.zb.myspringsecurity.config.security.ZxUser;
import com.zb.myspringsecurity.config.security.ZxUserDetailsServiceImpl;
import com.zb.myspringsecurity.service.IUserRoleService;
import com.zb.myspringsecurity.service.TokenService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Service;
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; @Service
public class ZbTokenAuthenticationFilter extends OncePerRequestFilter {
@Autowired
TokenService tokenService;
@Autowired
IUserRoleService iUserRoleService;
@Autowired
ZxUserDetailsServiceImpl userDetailsService; @Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
logger.info("TokenAuthenticationFilter.doFilterInternal start ...");
String token = request.getHeader("token"); if (token == null || "".equals(token)) {
logger.info("token is null , return .");
filterChain.doFilter(request, response);
return;
} if (SecurityContextHolder.getContext().getAuthentication() != null) {
filterChain.doFilter(request, response);
return;
} boolean result = tokenService.verifyToken(token);
if (!result) {
logger.info("ssoService.verifyToken not pass , return .");
filterChain.doFilter(request, response);
return;
} ZxUser zxUser = userDetailsService.loadUserByUsername(tokenService.getUserNameByToken(token)); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
zxUser, null, zxUser.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); logger.info("token valid pass , username : " + zxUser.getUsername());
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
}
}
  • 至此,我们已经把项目改造成前后端分离的了
  • 有数据库用户密码登录
  • 有登录生成token
  • 有校验url, header必须包含token

七、v7.0(权限控制)

我们上面已经把spring security的一个核心功能(认证)说完了,下面我们说授权。

1、增加权限表,关联表

DROP TABLE IF EXISTS `tb_permission`;

CREATE TABLE `tb_permission` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`en_name` varchar(50) DEFAULT NULL,
`cn_name` varchar(50) DEFAULT NULL,
`create_time` DATE DEFAULT NULL,
`create_id` int(11) DEFAULT NULL,
`update_time` date DEFAULT NULL,
`update_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `tr_role_permission`; CREATE TABLE `tr_role_permission` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`role_id` bigint(20) DEFAULT NULL,
`permission_id` bigint(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

2、初始化数据


insert into `tb_permission` (id, en_name, cn_name) values (1, 'system:user:read', '可读');
insert into `tb_permission` (id, en_name, cn_name) values (2, 'system:user:edit', '可修改'); insert into `tr_role_permission` (id, role_id, permission_id) values (1, 1, 1);
insert into `tr_role_permission` (id, role_id, permission_id) values (2, 1, 2);
insert into `tr_role_permission` (id, role_id, permission_id) values (3, 2, 1);

3、修改ZxUser

增加permissionSet

@Data
public class ZxUser extends User implements UserDetails { ... private Set<String> permissionSet; ...
}

4、修改loadUserByUsername方法

增加权限查询部分:

@Override
public ZxUser loadUserByUsername(String username) throws UsernameNotFoundException { List<User> userList = iUserService.lambdaQuery().eq(User::getUserName, username).list();
if (CollectionUtils.isEmpty(userList)) {
throw new UsernameNotFoundException("不存在的用户");
}
User user = userList.get(0);
ZxUser zxUser = new ZxUser();
zxUser.setUserName(user.getUserName());
zxUser.setPassword(passwordEncoder.encode(user.getPassword()));
zxUser.setId(user.getId()); List<SimpleGrantedAuthority> authorityList = new ArrayList<>(); // role
List<UserRole> userRoleList = iUserRoleService.lambdaQuery().eq(UserRole::getUserId, zxUser.getId()).list();
if (!CollectionUtils.isEmpty(userRoleList)) {
for (UserRole userRole : userRoleList) {
authorityList.add(new SimpleGrantedAuthority(String.valueOf(userRole.getRoleId())));
} // permission
List<Long> roleIdList = userRoleList.stream().map(UserRole::getRoleId).collect(Collectors.toList());
List<RolePermission> rolePermissionList = iRolePermissionService.lambdaQuery()
.in(RolePermission::getRoleId, roleIdList).list();
if (!CollectionUtils.isEmpty(rolePermissionList)) {
Collection<Permission> permissionList = iPermissionService
.listByIds(rolePermissionList.stream()
.map(RolePermission::getPermissionId).collect(Collectors.toList()));
Set<String> permissionSet = permissionList.stream().map(Permission::getEnName).collect(Collectors.toSet());
zxUser.setPermissionSet(permissionSet);
}
}
zxUser.setAuthorities(authorityList);
return zxUser;
}

5、我们实现一个自己的权限控制类

package com.zb.myspringsecurity.config.security.customer;

import com.zb.myspringsecurity.config.security.ZxUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component; import java.io.Serializable;
import java.util.Set; @Slf4j
@Component
public class ZbPermissionEvaluator implements PermissionEvaluator {
@Override
public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
ZxUser user = (ZxUser) authentication.getPrincipal();
Set<String> permissonSet = user.getPermissionSet();
if (permission == null) {
log.info("permission valid not pass , permission is null");
return false;
}
if (permissonSet.contains(permission.toString())) {
log.info("permission valid pass , permission : {}", permission.toString());
return true;
}
log.info("permission valid not pass , permission : {}", permission.toString());
return false;
} @Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
return false;
}
}
  • 很简单,就是判断权限集合存不存在当前权限

6、配置使之生效


@Autowired
ZbPermissionEvaluator zbPermissionEvaluator; @Bean
public DefaultWebSecurityExpressionHandler defaultWebSecurityExpressionHandler() {
DefaultWebSecurityExpressionHandler defaultWebSecurityExpressionHandler = new DefaultWebSecurityExpressionHandler();
defaultWebSecurityExpressionHandler.setPermissionEvaluator(zbPermissionEvaluator);
return defaultWebSecurityExpressionHandler;
} @Override
protected void configure(HttpSecurity httpSecurity) throws Exception { ... httpSecurity.authorizeRequests().expressionHandler(defaultWebSecurityExpressionHandler()); ... }

7、测试类

package com.zb.myspringsecurity.controller;

import com.zb.myspringsecurity.entity.User;
import com.zb.myspringsecurity.service.IUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest;
import java.util.List; @Slf4j
@RestController
@RequestMapping("/user")
public class UserController { @Autowired
IUserService iUserService; @PreAuthorize("hasPermission('UserController', 'system:user:read')")
@RequestMapping("/list")
public List<User> list(HttpServletRequest request) {
log.info("session id: {}" , request.getSession().getId());
return iUserService.list();
}
}

8、启动服务测试

  • 注意:我们上面的/user/list, 配置了'system:user:read'权限
  • 仔细去看我们初始化的数据库数据,会发现wangwu是没有任何角色的,也没有任何权限
  • 所以,wangwu 不能访问/user/list

先测试zhangsan:

POST http://localhost:8080/login/login-in
Accept: */*
Cache-Control: no-cache
content-type:application/json {"username":"zhangsan", "password":"123"}

返回:

{
"data": {
"token": "e048ba23-7061-43d6-ab35-7c2eb93acda8",
"expireTime": 3600
},
"code": 200,
"msg": "ok"
}

用这个token去请求/user/list


GET http://localhost:8080/user/list
Accept: application/json
token: e048ba23-7061-43d6-ab35-7c2eb93acda8

返回:

[
{
"id": 1,
"userName": "zhangsan",
"password": "123",
"mobile": null,
"sex": null,
"email": null,
"status": null,
"createTime": null,
"createId": null,
"updateTime": null,
"updateId": null
},
{
"id": 2,
"userName": "lisi",
"password": "123",
"mobile": null,
"sex": null,
"email": null,
"status": null,
"createTime": null,
"createId": null,
"updateTime": null,
"updateId": null
},
{
"id": 3,
"userName": "wangwu",
"password": "123",
"mobile": null,
"sex": null,
"email": null,
"status": null,
"createTime": null,
"createId": null,
"updateTime": null,
"updateId": null
}
]
  • 用同样的方法测试lisi和wangwu,lisi可以访问,wangwu不可以,返回如下
{
"timestamp": "2021-07-19T06:53:38.833+0000",
"status": 500,
"error": "Internal Server Error",
"message": "No message available",
"path": "/user/list"
}
  • 当然你还可以统一你的异常回复信息,可以自行研究
  • 至此,我们的权限控制也实现了

八、总结

  • 至此,我们的认证和鉴权都说完了
  • 以上只是一种实现,spring security支持自定义扩展,还有其它实现方式,可以自己研究
  • 代码放到github上了,关注公众号:丰极,回复:myspringsecurity获取

欢迎关注微信公众号:丰极,更多技术学习分享。

从零学习SpringSecurity的更多相关文章

  1. 老贼博客php教程从零学习PHP开始写作,顺祝新同事快乐!

    随笔是不是这样写的,好似是吧! 老贼博客php教程从零学习PHP开始写作,顺祝新同事快乐! 谢谢支持,点赞!

  2. 【从零学习openCV】IOS7下的人脸检測

    前言: 人脸检測与识别一直是计算机视觉领域一大热门研究方向,并且也从安全监控等工业级的应用扩展到了手机移动端的app,总之随着人脸识别技术获得突破,其应用前景和市场价值都是不可估量的,眼下在学习ope ...

  3. 【从零学习openCV】IOS7根据人脸检测

    前言: 人脸检測与识别一直是计算机视觉领域一大热门研究方向,并且也从安全监控等工业级的应用扩展到了手机移动端的app.总之随着人脸识别技术获得突破,其应用前景和市场价值都是不可估量的,眼下在学习ope ...

  4. WCF从零学习之设计和实现服务协定2

    WCF从零学习之设计和实现服务协定(二)   在创建服务协定之前,有很多WCF术语,比如: 消息.服务.终结点 创建协定 类或接口都可以定义服务协定,建议使用接口,因为接口可以直接对服务协定建模 服务 ...

  5. WCF从零学习之WCF概述(一)

    WCF从零学习之WCF概述(一) 一.WCF概述 我先了解了分布式应用程序开发,所谓分布式应用程序是指应用程序分布在不同计算机上,通过网络来共同完成一项任务.通常为服务器/客户端模式. 在WCF发布之 ...

  6. 从零学习Fluter(八):Flutter的四种运行模式--Debug、Release、Profile和test以及命名规范

    从零学习Fluter(八):Flutter的四种运行模式--Debug.Release.Profile和test以及命名规范 好几天没有跟新我的这个系列文章,一是因为这两天我又在之前的基础上,重新认识 ...

  7. 2、JavaScript 基础二 (从零学习JavaScript)

     11.强制转换 强制转换主要指使用Number.String和Boolean三个构造函数,手动将各种类型的值,转换成数字.字符串或者布尔值. 1>Number强制转换 参数为原始类型值的转换规 ...

  8. 1、JavaScript 基础一 (从零学习JavaScript)

    1:定义:javascript是一种弱类型.动态类型.解释型的脚本语言. 弱类型:类型检查不严格,偏向于容忍隐式类型转换. 强类型:类型检查严格,偏向于不容忍隐式类型转换. 动态类型:运行的时候执行类 ...

  9. 【从零学习openCV】IOS7人脸识别实战

    前言 接着上篇<IOS7下的人脸检測>,我们顺藤摸瓜的学习怎样在IOS7下用openCV的进行人脸识别,实际上非常easy,因为人脸检測部分已经完毕,剩下的无非调用openCV的方法对採集 ...

随机推荐

  1. VMware虚拟机CentOS磁盘扩容

    版本信息: VMware Workstation 15 Pro  15.5.2 build-15785246, CentOS7 原虚拟机默认20G,安装东西多了,磁盘空间不够用, docker mys ...

  2. IAP 订阅后端踩坑总结之 Google 篇

    前言: 本文利用 python 作为后端服务器, 且接入的 Google Cloud Pub/Sub 服务作为实时开发者通知, 未记录具体支付流程的代码,只记录了再开发过程中较为耗时,个人认为比较麻烦 ...

  3. Pptx的形状转为WPF的Geometry

    本文是将演示如何解析pptx文件的形状到WPF当中,并且绘制显示出来 安装Openxml sdk 首先,我们先安装nuget的openxml sdk,下面两种方式都可以安装: nuget包管理器控制台 ...

  4. 实时实例分割的Deep Snake:CVPR2020论文点评

    实时实例分割的Deep Snake:CVPR2020论文点评 Deep Snake for Real-Time Instance Segmentation 论文链接:https://arxiv.org ...

  5. MindSpore模型精度调优实践

    MindSpore模型精度调优实践 引论:在模型的开发过程中,精度达不到预期常常让人头疼.为了帮助用户解决模型调试调优的问题,为MindSpore量身定做了可视化调试调优组件:MindInsight. ...

  6. Wide-Bandgap宽禁带(WBG)器件(如GaN和SiC)市场将何去何从?

    Wide-Bandgap宽禁带(WBG)器件(如GaN和SiC)市场将何去何从? Where Is the Wide-Bandgap Market Going? 电力电子在采用宽禁带(WBG)器件(如 ...

  7. 简化可视SLAM应用程序的开发

    简化可视SLAM应用程序的开发 Easing the development of visual SLAM applications 同步定位和映射(SLAM)描述了一个设备(如机器人)使用传感器数据 ...

  8. .Net RabbitMQ实战指南——HTTP API接口调用

    RabbitMQ Management插件还提供了基于RESTful风格的HTTP API接口来方便调用.一共涉及4种HTTP方法:GET.PUT.DELETE和POST.GET方法一般用来获取如集群 ...

  9. 为什么我严重不建议去培训机构参加SAP培训?

    欢迎关注微信公众号:sap_gui (ERP咨询顾问之家) 关于是否要参加SAP培训的话题已经是老生常谈了,知乎上随便一搜有好多人在问是否要去参加SAP培训,底下已经有很多人在上面给出了正确建议.但也 ...

  10. 题解 P5318 【【深基18.例3】查找文献】

    题目传送门 根据本蒟蒻细致粗略的分析 这明显是一道水题模(du)板(liu)题 可我竟然没有一遍AC; 为更好地食用本题解需要了解以下内容 1.dfs(大法师深搜) 2.bfs(冰法师广搜)/dij最 ...