安全认证框架
Spring Security 是一个功能强大且高度可定制的身份验证和访问控制框架。它是保护基于 Spring 的应用程序的事实上的标准。
Spring Security 是一个专注于为 Java 应用程序提供身份验证和授权的框架。与所有 Spring 项目一样,Spring Security 的真正强大之处在于它可以轻松扩展以满足自定义要求
特征
作为权限管理框架,其内部机制可分为两大部分,其一是认证授权authorization,其二是权限校验authentication。
认证授权authorization是指,根据用户提供的身份凭证,生成权限实体,并为之授予相应的权限。
权限校验authentication是指,用户请求访问被保护资源时,将被保护资源所需的权限和用户权限实体所拥护的权限二者进行比对,如果校验通过则用户可以访问被保护资源,否则拒绝访问。
认证授权、权限校验(身份验证)
Spring Security 简介
Spring 是一个非常流行和成功的 Java 应用开发框架。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。
其他:oauth 单点登录
依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.9</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.offcn</groupId>
<artifactId>security-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!--web启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--security启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--开发工具-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!--mybatisplus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<!--数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
<!--连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.20</version>
</dependency>
<!--json转换-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.68</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<!--防止在部署时,映射文件.xml文件不能部署-->
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
</project>
权限设计 五张表 用户、角色、权限、用户角色、角色权限 资源和权限
通过 URL 做 数据操作
项目可以分模块 classpath 读其他模块的配置文件*
mybatis 配置
逆向生成
package com.offcn.test;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
public class GeneratorTest {
public static void main(String[] args) {
// 代码生成器
AutoGenerator mpg = new AutoGenerator();
// 全局配置
GlobalConfig gc = new GlobalConfig();
gc.setOutputDir("D:\\bh linfo\\IdeaProjects\\day34\\security-demo\\src\\main\\java");
gc.setAuthor("zs");
gc.setOpen(false);
//实体属性 Swagger2 注解
//gc.setSwagger2(false);
mpg.setGlobalConfig(gc);
// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql:///securitydemo?characterEncoding=utf8");
dsc.setDriverName("com.mysql.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("admin");
mpg.setDataSource(dsc);
// 包配置
PackageConfig pc = new PackageConfig();
pc.setParent("com.offcn");
pc.setEntity("pojo");
pc.setMapper("dao");
pc.setController("controller");
mpg.setPackageInfo(pc);
// 配置模板
TemplateConfig templateConfig = new TemplateConfig();
//默认关闭不需要的生成内容
templateConfig.setXml(null);
templateConfig.setService(null);
templateConfig.setServiceImpl(null);
mpg.setTemplate(templateConfig);
// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setSuperEntityClass("com.baomidou.mybatisplus.extension.activerecord.Model");
strategy.setEntityLombokModel(true);
strategy.setRestControllerStyle(true);
String tableNames="sys_role,sys_role_menu,sys_menu,sys_user,sys_user_role";
strategy.setInclude(tableNames.split(","));
strategy.setControllerMappingHyphenStyle(true);
strategy.setTablePrefix("t_");
mpg.setStrategy(strategy);
mpg.execute();
}
}
配置文件
#端口、应用名称
server:
port: 8003
#数据源
spring:
application:
name: security-demo
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql:///securitydemo?characterEncoding=utf8
username: root
password: admin
devtools:
restart:
enabled: true
additional-paths: src/main/java
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
#mybatis-plus
mybatis-plus:
type-aliases-package: com.offcn.pojo
mapper-locations: classpath:/mappers/*.xml
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
name=username 名称固定
name=password 名称固定
action=/login 名称固定
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
</head>
<body>
<!--action名称固定-->
<form method="post" action="/login">
<!--name=username名称固定-->
<p>用户名:<input name="username"></p>
<!--name=password名称固定-->
<p>密码:<input type="password" name="password"></p>
<p><input type="submit" value="提交"></p>
</form>
</body>
</html>
a href
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<a href="user/list">查询</a>
<hr>
<a href="user/add">增加</a>
<hr>
<a href="user/update">修改</a>
<hr>
<a href="user/delete">删除</a>
<hr>
<a href="/logout">退出登录</a>
</body>
</html>
Controller
@Secured("ROLE_user") 验证角色
@PreAuthorize("hasAuthority('user:add')") 验证权限
@RestController
@RequestMapping("user")
public class SysUserController {
//添加角色校验的注解 -- 即当使用者用户在页面中点击对应的连接的时候,验证当前登录用户是否存在指定的角色
//@Secured("ROLE_user")
@GetMapping("list")
public ResultData list(){
return ResultData.ok();
}
//添加权限校验的注解 -- 即当使用者用户在页面中点击对应的连接的时候,验证当前登录用户是否存在指定的权限
@PreAuthorize("hasAuthority('user:add')")
@GetMapping("add")
public ResultData add(){
return ResultData.ok();
}
@GetMapping("update")
public ResultData update(){
return ResultData.ok();
}
@GetMapping("delete")
public ResultData delete(){
return ResultData.ok();
}
}
密文 把原始密码通过加密算法计算得出结果(md5、Password、Bcrypt)
Rounds 增加盐值 破解更麻烦
一般不可逆(彩虹表暴力破解 穷举)
访问什么资源都需要先登录、验证权限(底层:Session)
本身提供了登录页面
SecurityConfig
//允许security读取相关配置
@EnableWebSecurity
//允许在controller层的方法上面添加security框架相关的注解
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//bean -- spring容器管理的对象
@Resource
private CustomUserDetailService userDetailService;
@Resource
private AuthenticationFailureHandler authenticationFailureHandler;
@Resource
private AuthenticationEntryPoint authenticationEntryPoint;
@Resource
private AccessDeniedHandler accessDeniedHandler;
//加密对象
@Bean
public BCryptPasswordEncoder getPasswordEncoder(){
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
return passwordEncoder;
}
//认证器对象-配置
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
//捆绑两个对象 -- 底层实现返回来的数据库中的密文和前端页面输入的原始密码被加密之后形参的密文,两者对比
auth.userDetailsService(userDetailService).passwordEncoder(getPasswordEncoder());
}
//全局配置
@Override
public void configure(HttpSecurity http) throws Exception {
//跨站请求伪造
http.csrf().disable()//关闭跨站请求伪造
//指定登录页面是谁、指定登录成功之后跳转的页面是谁
.formLogin()//使用表单登录实现认证
.loginPage("/login.html")//登录页面是哪个
.loginProcessingUrl("/login")//登录连接是哪个
.defaultSuccessUrl("/index.html")//登录成功跳转页面
.permitAll()
.failureHandler(authenticationFailureHandler)//登录失败如何处理
.and()//添加其他配置
.authorizeRequests()//请求需要认证
//哪些资源不需要登录可以直接访问
.antMatchers("/login.html","/login").permitAll()//代表哪些页面或者url不需要登录可以直接访问
.antMatchers("/js/**","/css/**","/img/**","/favicon.ico").permitAll()//代表静态资源不需要登录可以直接访问
.anyRequest()//代表当前项目任何请求都要到此处
.authenticated();//代表需要认证
//发生异常时如何处理
http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).accessDeniedHandler(accessDeniedHandler);
//登出 -- 退出登录的配置
http.logout().logoutUrl("/logout").logoutSuccessUrl("/login.html");
}
}
提示用户名或者密码有误 一般提示不会特别明确 防止别有用心的人
CustomUserDetailService 只匹配用户名 密文密码捆绑送回,认证器对象对照密码
CustomUserDetailService
@Component
public class CustomUserDetailService implements UserDetailsService {
@Autowired
private SysUserMapper sysUserMapper;
@Autowired
private SysRoleMapper sysRoleMapper;
@Autowired
private SysMenuMapper sysMenuMapper;
//认证操作 -- 做登录(验证)和授权
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//通过实参获取到了从登录页面送来的用户名
//到用户表中根据用户名查找用户信息 -- 可能查到、可能没有查到
QueryWrapper<SysUser>wrapper = new QueryWrapper<>();
wrapper.eq("user_name",username);
SysUser sysUser = sysUserMapper.selectOne(wrapper);
//没有查找认证失败 -- 要么返回到登录页面、要么去到提示页面
if(sysUser == null){
System.out.println("************");
throw new UsernameNotFoundException("用户名或者密码有误");
}
//如果查到认证成功
Integer userId = sysUser.getId();
//授权 -- 给当前用户绑定角色、绑定权限、绑定资源
List<GrantedAuthority> list = new ArrayList<>();
List<SysRole> roleList = sysRoleMapper.selectRoleByUserID(userId);
for(SysRole sysRole : roleList){
String roleCode = sysRole.getRoleCode();//admin
SimpleGrantedAuthority authority = new SimpleGrantedAuthority("ROLE_" + roleCode);
list.add(authority);
}
List<SysMenu> menuList = sysMenuMapper.selectMenuByUserID(userId);
for(SysMenu sysMenu : menuList){
String perms = sysMenu.getPerms();
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(perms);
list.add(authority);
}
//返回登录用户信息 -- 包含用户信息、角色、权限、资源 -- 用户信息也会保存到session
return new User(username,sysUser.getPassword(),list);
}
}
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
response.setContentType("application/json;charset=UTF-8");
PrintWriter writer = response.getWriter();
writer.write(JSON.toJSONString(ResultData.error()));
}
}
///////////////////////
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
response.setContentType("application/json;charset=UTF-8");
PrintWriter writer = response.getWriter();
writer.write(JSON.toJSONString(ResultData.error()));
}
}
///////////////////////
@Component
public class AuthenticationFailureHandlerImpl implements AuthenticationFailureHandler {
//登录失败如何操作
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
response.setContentType("application/json;charset=UTF-8");
PrintWriter writer = response.getWriter();
writer.write(JSON.toJSONString(ResultData.error()));
}
}