SpringBoot的安全
常用框架:Shrio,SpringSecurity
两个功能:
- Authentication 认证
- Authorization 授权
权限:
- 功能权限
- 访问权限
- 菜单权限
原来用拦截器、过滤器来做,代码较多。现在用框架。
SpringSecurity
只要引入就可以使用
可以在官网看教程
几个重要的类:
- WebSecurityConfigurerAdapter 自定义Security策略
- AuthenticationManagerBuilder 自定义认证策略
- @EnableWebSercurity
基本操作
springboot 2.7.0前
继承WebSecurityConfigurerAdapter
重写configure(HttpSecurity http)方法——授权
使用链式编程
第一个小例子:
http.authorizeRequests() //自定义权限控制
.antMatchers("/").permitAll() //所有人可以访问首页
.addMatchers("/vip1").hasRole("vip1"); //vip1可以访问
//登录,也可以使用and()拼接
http.fromLogin(); //没有权限会自动跳转到登录页面,即使没有写过/login
源码:默认的login和login?error
重写Configure(AuthenticationManagerBuilder auth)方法——认证
2.7.0版本后,WebSecurityConfigurerAdapter被弃用
上面配置好了什么权限可以做什么事情,这里则用来做登录控制和权限查询操作
protected void configure(AuthenticationManagerBuilder auth){
//因为反编译,新版要求所有密码必须加密
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
//JDBC认证 例子为blog_system
auth.jdbcAuthentication().passwordEncoder(encoder)
.dataSource(dataSource)
.usersByUsernameQuery(userSQL) //通过username、password、enabled登录控制
.authoritiesByUsernameQuery(authoritySQL); //通过用户名、权限(在查询的第二列)获取role
//内存认证 例子来自狂神说课堂笔记,没用数据库的例子
auth.inMemoryAuthentication().passwordEncoder(encoder)
.withUSer("name").password( new BCryptPasswordEncoder("123456")).roles("vip2","vip3")
.and() //通过and拼接其他用户
.withUSer("name2").password(new BCryptPasswordEncoder("123456")).roles("vip2","vip3");
//自定义认证规则,接受Service参数 例子来自VBlog
auth.userDetailsService(userService);
}
上面的最后一种方式:UserService继承UserDetailService,并重写方法
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
User user = userMapper.loadUserByUsername(s);
if (user == null) {
//避免返回null,这里返回一个不含有任何值的User对象,在后期的密码比对过程中一样会验证失败
return new User();
}
//查询用户的角色信息,并返回存入user中
List<Role> roles = rolesMapper.getRolesByUid(user.getId());
user.setRoles(roles);
return user;
}
登录之后若没权限,就是跳转到没权限界面了
基于方法的动态权限
将用户的权限保存在数据库中,并实现动态权限控制。
在配置类上使用@EnableGlobalMethodSecurity
来开启它;
然后在方法中使用@PreAuthorize
配置访问接口需要的权限;
@PreAuthorize("hasAuthority('pms:product:create')")
权限字符串自定。
再从数据库中查询出用户所拥有的权限值设置到UserDetails对象中去。
ruoyi项目使用了这种方法
缺点:方法权限写死在代买里了,不好维护。也可以通过过滤器、拦截器实现,
@Since 2.7.0 新用法
新用法非常简单,无需再继承WebSecurityConfigurerAdapter
,只需直接声明配置类,再配置一个生成SecurityFilterChain
Bean的方法,把原来的HttpSecurity配置移动到该方法中即可。
/**
* SpringSecurity 5.4.x以上新用法配置
* 为避免循环依赖,仅用于配置HttpSecurity
* Created by macro on 2022/5/19.
*/
@Configuration
public class SecurityConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
//省略HttpSecurity的配置
return httpSecurity.build();
}
}
Security上下文
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
注销
前端请求/logout
http.logout();
//会请求/logout,可以自定义url。默认回到/login?logout
//看源码,可以清空cookies和session
http.logout().logoutUrl("/") //注销成功回首页
.csrf().disable() //关闭防止csrf攻击 csrf:跨站请求攻击,可能屏蔽get。
模板引擎相关功能
页面定制
登录:loginPage(“”),改完之后默认的login就没有了
点进去看源码
/*
.formLogin(formLogin ->
* formLogin
* .usernameParameter( username ) //默认
* .passwordParameter( password )
* .loginPage( /authentication/login ) 登录路由
* .failureUrl(/authentication/login?failed)
* .loginProcessingUrl( /authentication/login/process ) 登陆验证页面
* );
*/
“记住我”功能
http.rememberMe(); //登录页会有“记住我” 保存cookie 默认14天
Shiro
Apache 开源框架
三大对象:
- Subject 用户
- SecurityManager 管理用户
- Realm 连接数据
Subject:主体,代表了当前 “用户”,与当前应用交互的任何东西都是 Subject,如网络爬虫,人等;即一个抽象概念;所有 Subject 都绑定到 SecurityManager,与 Subject 的所有交互都会委托给 SecurityManager;可以把 Subject 认为是一个门面;SecurityManager 才是实际的执行者;
SecurityManager:安全管理器;即所有与安全有关的操作都会与 SecurityManager 交互;且它管理着所有 Subject;可以看出它是 Shiro 的核心,它负责与后边介绍的其他组件进行交互,如果学习过 SpringMVC,你可以把它看成 DispatcherServlet 前端控制器;
Realm:域,Shiro 从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色 / 权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource,即安全数据源。
也就是说对于我们而言,最简单的一个 Shiro 应用:
- 应用代码通过 Subject 来进行认证和授权,而 Subject 又委托给 SecurityManager;
- 我们需要给 Shiro 的 SecurityManager 注入 Realm,从而让 SecurityManager 能得到合法的用户及其权限进行判断。
<!--shiro依赖- SSM-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.4</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.2.4</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.2.4</version>
</dependency>
shiro-web提供了Web集成的支持,其通过一个 ShiroFilter 入口来拦截需要安全控制的 URL,然后进行相应的控制
1.编写配置类
@Configuration
public class ShiroConfig{
//ShiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean hetShiroFilterFactoryBean(@Qualifier DefaultWebSercurityManager defaultWebSercurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//设置安全管理器
bean.setSecurityManager(defau+ltWebSercurityManager);
Map<String,String> filterMap = new LinkedHashMap<>();
filterMap.put("","authc")
return bean;
}
//DefaultWebSercurityManager
@Bean
public DefaultWebSercurityManager getDefaultWebSercurityManager(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSercurityManager sercurityManager new DefaultWebSercurityManager();
securityManager.setRealm(userRealm);
return sercurityManager;
}
//Realm 自定义
@Bean
public Realm userRealm(){
return new UserRealm();
}
}
public UserRealm extends AuthorizingRealm{
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 用户名
String username = (String) token.getPrincipal();
// 密码
String password = new String((char[]) token.getCredentials());
Userlogin userlogin = null;
try {
userlogin = userloginService.findByName(username);
} catch (Exception e) {
e.printStackTrace();
}
if (userlogin == null) {
// 没有该用户名
throw new UnknownAccountException();
} else if (!password.equals(userlogin.getPassword())) {
// 密码错误
throw new IncorrectCredentialsException();
}
// 身份验证通过,返回一个身份信息
AuthenticationInfo aInfo = new SimpleAuthenticationInfo(username, password, getName());
return aInfo;
}
}
2.登录控制
//登录表单处理
@RequestMapping(value = "/login", method = {RequestMethod.POST})
public String login(Userlogin userlogin) throws Exception {
//Shiro实现登录
UsernamePasswordToken token = new UsernamePasswordToken(userlogin.getUsername(),
userlogin.getPassword());
Subject subject = SecurityUtils.getSubject();
//如果获取不到用户名就是登录失败,但登录失败的话,会直接抛出异常
//登录
subject.login(token);
if (subject.hasRole("admin")) {
return "redirect:/admin/showStudent";
} else if (subject.hasRole("teacher")) {
return "redirect:/teacher/showCourse";
} else if (subject.hasRole("student")) {
return "redirect:/student/showCourse";
}
return "/login";
}