关于作者
金山老师:从事Java、大数据、Python职业化培训6年,项目管理、软件开发14年。欢迎添加我的微信号【jshand】,最近建了一些微信交流群,添加【指尖架构师】公众号,回复:进群
部分讲课录屏已经上传到bilibili,欢迎搜索“是金山老师”
文章目录
权限管理
- 授权
- 鉴权
授权
给不同的用户身份分配不同的权限(可以操作哪些菜单、按钮)
- 用户管理的基础数据管理
- 菜单的基本信息
- 角色的基本信息
- 给角色分配菜单
- 给用户分配角色
维护完上述信息,开发查询权限菜单的功能
鉴权
接口端需要添加鉴别用户身份是否能够执行某一个请求。
- shiro : Apache 稍微轻量级的框架
- SpringSecurity : 扩展性比较好,支持OAUTH2登录等。
角色信息的维护
菜单维护
@TableField(exist = false) // 如果不添加TableField 影响单表的CRUD
private List<Menu> children;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Upf65i1C-1651461037732)(images/权限管理/image-20220424095736984.png)]
- 查询定级菜单
- 查询下级菜单
SpringSecurity
https://docs.spring.io/spring-security/site/docs/5.4.10/reference/html5/#samples
认证、授权
入门
创建一个springboot的项目 ,创建一个Controller
- maven
- parent
- 启动器(starter-web)
- controller
- app
使用SpringSecurit
添加一个依赖
starter-security
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
springboot项目添加starter-security默认请求必须登录(user +动态密码)
自动配置使用DelegatingFilterProxy加载Filter
概念
SpringSecurity基于Filter的概念实现的过滤器的功能
-
用到了DelegatingFilterProxy类去加载 FilterChain
-
FilterChainProxy:包含一些拦截器 SecurityFilterChain
内置的Filter
- ChannelProcessingFilter
- WebAsyncManagerIntegrationFilter
- SecurityContextPersistenceFilter
- HeaderWriterFilter
- CorsFilter
- CsrfFilter
- LogoutFilter
- OAuth2AuthorizationRequestRedirectFilter
- Saml2WebSsoAuthenticationRequestFilter
- X509AuthenticationFilter
- AbstractPreAuthenticatedProcessingFilter
- CasAuthenticationFilter
- OAuth2LoginAuthenticationFilter
- Saml2WebSsoAuthenticationFilter
UsernamePasswordAuthenticationFilter
- OpenIDAuthenticationFilter
- DefaultLoginPageGeneratingFilter
- DefaultLogoutPageGeneratingFilter
- ConcurrentSessionFilter
DigestAuthenticationFilter
- BearerTokenAuthenticationFilter
BasicAuthenticationFilter
- RequestCacheAwareFilter
- SecurityContextHolderAwareRequestFilter
- JaasApiIntegrationFilter
- RememberMeAuthenticationFilter
- AnonymousAuthenticationFilter
- OAuth2AuthorizationCodeGrantFilter
- SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor
- SwitchUserFilter
- UsernamePasswordAuthenticationFilter:基于用户名密码的登录的过滤器
- Authentication
- ExceptionTranslationFilter: 登录失败、没有权限
- FilterSecurityInterceptor:授权(鉴权)
简单的执行逻辑
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QAwGSynk-1651461037738)(images/权限管理/image-20220427142756280.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fid0faNn-1651461037740)(images/权限管理/image-20220427142721850.png)]
配置用户
- 在配置文件中配置
- 默认user / 2a096f60-0288-4f9b-bac3-7f1c1bd878b4
server:
port: 8081
spring:
security:
user:
name: root
password: 666666
- Java形式配置
配置PasswordEncoder
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
PasswordEncoder passwordEncoder;
@Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
//配置认证管理器
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//构造一个超级管理员的角色
List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("Admin");
//$2a$10$Jv1lgSebvvdieROLHeZqR.BzuhxwmbnhtVaAe7YaY6kIEIn2YBu8y
//noop
// auth.inMemoryAuthentication().withUser("admin").password("{noop}888888").authorities(authorities);
String password = passwordEncoder.encode("666666");
System.out.println("password = " + password);
auth.inMemoryAuthentication().withUser("admin").password(password).authorities(authorities);
//888888 -->转换成密文
//设置一个类,用户查询数据库
// auth.userDetailsService()
}
}
从数据库中查询用户
准备用户表
USE `ssm_java4`;
/*Table structure for table `user` */
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`user_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '医生id',
`username` varchar(100) DEFAULT NULL COMMENT '用户名',
`password` varchar(100) DEFAULT NULL COMMENT '密码',
`realname` varchar(100) DEFAULT NULL COMMENT '真实姓名',
`telephone` varchar(20) DEFAULT NULL COMMENT '电话号码',
`dept_id` int(11) DEFAULT NULL COMMENT 'id',
`user_type` int(11) DEFAULT NULL COMMENT '医生类型',
`lastlogin` datetime DEFAULT NULL COMMENT '最后登录时间',
`active` int(11) DEFAULT '1' COMMENT '是否有效,1 有效,0 失效',
`createtime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=46 DEFAULT CHARSET=utf8mb4 COMMENT='医生-用户信息';
/*Data for the table `user` */
insert into `user`(`user_id`,`username`,`password`,`realname`,`telephone`,`dept_id`,`user_type`,`lastlogin`,`active`,`createtime`) values (2,'root','123456','真实姓名2222',NULL,NULL,NULL,'2022-04-26 14:36:26',1,'2022-03-15 10:41:51'),(6,'admin','123456','赵云','',NULL,NULL,'2022-04-26 13:36:37',1,'2022-03-15 10:49:42'),(17,'userabc01','12345601','01测试分页1',NULL,NULL,NULL,NULL,1,'2022-04-02 15:52:26'),(18,'userabc02','12345602','02测试分页1',NULL,NULL,NULL,NULL,1,'2022-04-02 15:52:26'),(19,'userabc03','12345603','03测试分页1',NULL,NULL,NULL,NULL,1,'2022-04-02 15:52:26'),(20,'userabc04','12345604','04测试分页1',NULL,NULL,NULL,NULL,1,'2022-04-02 15:52:26'),(21,'userabc05','12345605','05测试分页1',NULL,NULL,NULL,NULL,1,'2022-04-02 15:52:26'),(22,'userabc06','12345606','06测试分页1',NULL,NULL,NULL,NULL,1,'2022-04-02 15:52:26'),(23,'userabc07','12345607','07测试分页1',NULL,NULL,NULL,NULL,1,'2022-04-02 15:52:26'),(24,'userabc08','12345608','08测试分页1',NULL,NULL,NULL,NULL,1,'2022-04-02 15:52:26'),(25,'userabc09','12345609','09测试分页1',NULL,NULL,NULL,NULL,1,'2022-04-02 15:52:26'),(26,'userabc10','12345610','10测试分页1',NULL,NULL,NULL,NULL,1,'2022-04-02 15:52:26'),(27,'userabc11','12345611','11测试分页1',NULL,NULL,NULL,NULL,1,'2022-04-02 15:52:26'),(28,'userabc12','12345612','12测试分页1',NULL,NULL,NULL,NULL,1,'2022-04-02 15:52:26'),(29,'userabc13','12345613','13测试分页1',NULL,NULL,NULL,NULL,1,'2022-04-02 15:52:26'),(30,'userabc14','12345614','14测试分页1',NULL,NULL,NULL,NULL,1,'2022-04-02 15:52:26'),(31,'userabc15','12345615','15测试分页1',NULL,NULL,NULL,NULL,1,'2022-04-02 15:52:26'),(32,'userabc16','12345616','16测试分页1',NULL,NULL,NULL,NULL,1,'2022-04-02 15:52:26'),(33,'userabc17','12345617','17测试分页1',NULL,NULL,NULL,NULL,1,'2022-04-02 15:52:26'),(34,'userabc18','12345618','18测试分页1',NULL,NULL,NULL,NULL,1,'2022-04-02 15:52:26'),(35,'userabc19','12345619','19测试分页1',NULL,NULL,NULL,NULL,1,'2022-04-02 15:52:26'),(36,'userabc20','12345620','20测试分页1',NULL,NULL,NULL,NULL,1,'2022-04-02 15:52:26'),(37,'userabc21','12345621','21测试分页1',NULL,NULL,NULL,NULL,1,'2022-04-02 15:52:26'),(38,'userabc22','12345622','22测试分页1',NULL,NULL,NULL,NULL,1,'2022-04-02 15:52:26'),(39,'userabc23','12345623','23测试分页1',NULL,NULL,NULL,NULL,1,'2022-04-02 15:52:26'),(40,'userabc24','12345624','24测试分页1',NULL,NULL,NULL,NULL,1,'2022-04-02 15:52:26'),(41,'userabc25','12345625','25测试分页1',NULL,NULL,NULL,NULL,1,'2022-04-02 15:52:26'),(42,'userabc26','12345626','26测试分页1',NULL,NULL,NULL,NULL,1,'2022-04-02 15:52:26'),(43,'userabc27','12345627','27测试分页1',NULL,NULL,NULL,NULL,1,'2022-04-02 15:52:26'),(44,'userabc28','12345628','28测试分页1',NULL,NULL,NULL,NULL,1,'2022-04-02 15:52:26'),(45,'userabc29','12345629','29测试分页1',NULL,NULL,NULL,NULL,1,'2022-04-02 15:52:26');
整合mybatisplus
-
添加依赖
-
mybatisplus-starters
-
数据库的依赖
-
lombok
-
-
实体类
-
Mapper接口
-
Mapper.xml
-
包扫描
-
实现service
-
配置数据源
自定义查询数据库的方式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m1GRvAtx-1651461037742)(images/权限管理/image-20220428091556381.png)]
- 重写UserDetailsService接口, loadUserByUsername, 通过用户名查询数据,返回的接口需要封装到UserDetails
- 使用AuthenticationManagerBuilder配置UserDetailsService
package com.neuedu.boot.security.security;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.neuedu.boot.security.entity.User;
import com.neuedu.boot.security.service.IUserService;
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;
@Service
public class MyUserDetailsService implements UserDetailsService {
@Autowired
IUserService userService;
/**
* 根据用户名查询数据库的身份信息
* @param username
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//根据用户名,查询用户身份信息,
QueryWrapper wrapper = new QueryWrapper();
wrapper.eq("username",username);
User user = userService.getOne(wrapper);
if(user == null){
throw new UsernameNotFoundException("用户名或者密码不正确,再想想吧");
}
return new LoginUser(user);
}
}
package com.neuedu.boot.security.config;
import com.neuedu.boot.security.security.MyUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import java.util.List;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
PasswordEncoder passwordEncoder;
@Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Autowired
MyUserDetailsService userDetailsService;
//配置认证管理器
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//构造一个超级管理员的角色
List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("Admin");
//$2a$10$Jv1lgSebvvdieROLHeZqR.BzuhxwmbnhtVaAe7YaY6kIEIn2YBu8y
//noop
// auth.inMemoryAuthentication().withUser("admin").password("{noop}888888").authorities(authorities);
// String password = passwordEncoder.encode("666666");
// System.out.println("password = " + password);
//
// auth.inMemoryAuthentication().withUser("admin").password(password).authorities(authorities);
//888888 -->转换成密文
//设置一个类,用户查询数据库
auth.userDetailsService(userDetailsService);
}
}
用户认证的时候执行的Filter
UsernamePasswordAuthenticationFilter
Authentication:用户认证的身份
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DxWVCUV9-1651461037744)(images/权限管理/image-20220428095232638.png)]
- DaoAuthenticationProvider: 负责查询数据
整合JWT
- 重写登录的接口
- 登录成功生成jwt数据,发送给客户端(vuejs-axios)
- 解析header中的jwt
- 每次发送的时候axios中的header会携带 token
自定义登录接口
需要jwt的依赖
<!--整合jwt-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.19.1</version>
</dependency>
定义通用的json类型的返回接口CommonResult
定义LoginController,重写 UsernamePasswordAuthenticationFilter
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w5ZRDRIV-1651461037749)(images/权限管理/image-20220429085654931.png)]
package com.neuedu.boot.security.controller;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.neuedu.boot.security.config.CommonResult;
import com.neuedu.boot.security.entity.User;
import com.neuedu.boot.security.security.LoginUser;
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.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.ZoneId;
@RestController
public class LoginController {
@Autowired
AuthenticationManager authenticationManager;
@RequestMapping("/user/login")
CommonResult login(String username,String password){
//利用SpringSecurity的验证机制
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
Authentication authenticate = authenticationManager.authenticate(authRequest);
//如果authenticate为空,则说明登录失败
if(authenticate != null){
/**
* 如果登录成功,生成jwt
*/
//获取登录状态
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
User user = loginUser.getUser();//对一个数据库的那个实体
//使用用户身份生成jwt token ,发送个浏览器
String secret = "abcdef";
long longLastlogin = user.getLastlogin().atZone(ZoneId.systemDefault()).toInstant().getEpochSecond();
String token = JWT.create().
withClaim("userId", user.getUserId()). //设置登录用户id
withClaim("username", user.getUsername()). //设置登录的用户名
withClaim("lastlogin", longLastlogin). //设置最后一次登录时间
sign(Algorithm.HMAC256(secret));
return CommonResult.success(token);
}else{
return CommonResult.failed("用户名或密码不正确");
}
}
}
配置定义的登录接口
package com.neuedu.boot.security.config;
import com.neuedu.boot.security.security.MyUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import java.util.List;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
PasswordEncoder passwordEncoder;
@Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Autowired
MyUserDetailsService userDetailsService;
//配置认证管理器
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//构造一个超级管理员的角色
List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("Admin");
//$2a$10$Jv1lgSebvvdieROLHeZqR.BzuhxwmbnhtVaAe7YaY6kIEIn2YBu8y
//noop
// auth.inMemoryAuthentication().withUser("admin").password("{noop}888888").authorities(authorities);
//$2a$10$Jv1lgSebvvdieROLHeZqR.BzuhxwmbnhtVaAe7YaY6kIEIn2YBu8y
// String password = passwordEncoder.encode("666666");
// System.out.println("password = " + password);
//
// auth.inMemoryAuthentication().withUser("admin").password(password).authorities(authorities);
//888888 -->转换成密文
//设置一个类,用户查询数据库
auth.userDetailsService(userDetailsService);
}
/**
* 在IOC容器中定义Bean AuthenticationManager
* @return
* @throws Exception
*/
@Bean()
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// //关闭
// http.csrf().disable();
// //关闭session策略
// http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// //允许匿名访问 /user/login
// http.authorizeRequests().antMatcher("/user/login").anonymous();
// //其他请求都需要验证
// http.authorizeRequests().anyRequest().authenticated();
//上面是普通的写法,因为http对象支持builder模式,所以可以如下写法:
http.
//关闭 csrf
csrf().disable().
//关闭session策略
sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).
//允许 /user/login匿名访问
and().authorizeRequests().antMatcher("/user/login").anonymous().
//除了以上配置,其他的请求都需要登录
.anyRequest().authenticated();
}
}
从header中自定义获取jwt
SecurityContext 待续