Spring Security
介绍
Spring Security是一个功能强大、高度可定制性的身份验证和访问控制的框架。它能够保护基于Spring框架的应用程序。
Spring Security是一个专注于为 Java 应用程序提供身份验证和授权的框架。与所有Spring项目一样,Spring安全的真正威力在于它可以很容易地被扩展以满足需求。
整合流程
简单介绍整合流程:
本文案例使用 Spring Boot 集成 Spring Security。首先导入 security 的启动包,创建 security 的配置类,加上@EnableWebSecurity
注解,并继承WebSecurityConfigurerAdapter
抽象类,重写授权与用户认证的方法。
在授权方法中能够配置 用户访问某一 url 所具备的角色、前往登录页面的 url、注销成功后前往的 url、开启记住我功能 等。
在认证方法中可以配置 多个用户的用户名、密码、角色等信息、密码加密方式,用户信息可以从内存中获取,也能从数据库中获取。
代码结构与案例:
想要实现多个不同角色的用户想要动态访问 level1-level3 包下的页面,管理员所有页面都可访问,而其他用户通过角色来判断能否访问。
流程与代码:
首先加入启动器,当前 Spring Boot 版本使用 <version>2.2.6.RELEASE</version>
:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
创建配置类,加上注解@EnableWebSecurity
,继承WebSecurityConfigurerAdapter
,重写方法:
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//认证方法
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
super.configure(auth);
}
//授权方法
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
}
}
授权方法:
authorizeRequests()
授权多个请求antMatchers("/").permitAll()
配置 / 的 url 所有用户都可访问,当前案例 / 指向的就是首页hasRole("XXX")
配置访问该 url 所需的角色formLogin()
设定没有权限会默认去登录页面,默认访问/login
请求loginPage("/toLogin")
设定去登录页面的 url,没权限直接访问/toLogin
请求loginProcessingUrl("/login")
设定点击登录按钮后访问的 url,与<form th:action="@{/login}" method="post">
的请求路径一致,在 controller 中不需定义 login 方法,security 会自动帮我们进行登录操作usernameParameter("XXX")
与passwordParameter("XXX")
设置 form 登录表单中<input type="text" name="userName">
的 name 属性
@Override
protected void configure(HttpSecurity http) throws Exception {
//请求授权的规则
http.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level3/**").hasRole("vip3");
http.formLogin().loginPage("/toLogin")
.loginProcessingUrl("/login")
.usernameParameter("userName")
.passwordParameter("pwd");
/* csrf(跨站请求伪造) 注销时报404问题的原因之一 */
http.csrf().disable();
//设置注销成功去往的页面路径
http.logout().logoutSuccessUrl("/");
//开启rememberme功能
http.rememberMe().rememberMeParameter("remember");
}
认证方法:
withUser("admin")
配置用户名passwordEncoder(new XXXPasswordEncoder())
设置密码加密方式,实现类例如:
password(new BCryptPasswordEncoder().encode("123456"))
设置加密方式与密码roles("vip1", "vip2", "vip3")
设置当前用户角色信息and()
用于拼接下一个用户
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//inMemoryAuthentication() 从内存中获取认证信息
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("admin")
.password(new BCryptPasswordEncoder().encode("123456"))
.roles("vip1", "vip2", "vip3")
.and()
.withUser("zhangsan")
.password(new BCryptPasswordEncoder().encode("123456"))
.roles("vip1")
.and()
.withUser("lisi")
.password(new BCryptPasswordEncoder().encode("123456"))
.roles("vip1", "vip2");
}
到这 security 配置类就写好了,用 admin 登录后:
当然 Thymeleaf 与 Spring Security 也可以整合,例如 zhangsan 用户只有 vip1 角色,在页面上应该只显示 level1 模块,Thymeleaf 如何在 html 页面中整合 Spring Security,看以下流程:
Thymeleaf 与 Spring Security 整合
首先导入整合依赖:
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>
html 引入 springsecurity 命名空间:
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
div 标签中加入角色判断:
页面中是否显示的 div 块,加入sec:authorize="hasRole('vip1')"
,例如 level1:
<div class="column" sec:authorize="hasRole('vip1')">
<div class="ui raised segment">
<div class="ui">
<div class="content">
<h5 class="content">Level 1</h5>
<hr>
<div><a th:href="@{/level1/1}"><i class="bullhorn icon"></i> Level-1-1</a></div>
<div><a th:href="@{/level1/2}"><i class="bullhorn icon"></i> Level-1-2</a></div>
<div><a th:href="@{/level1/3}"><i class="bullhorn icon"></i> Level-1-3</a></div>
</div>
</div>
</div>
</div>
该案例的 github 链接:Spring Security
Shiro
介绍
Apache Shiro(发音为“shee roh”,日语中的“castle”一词)是一个功能强大且易于使用的Java安全框架,它执行身份验证、授权、加密和会话管理,可用于保护任何应用程序,从命令行应用程序,移动应用程序到最大的web和企业应用程序。
Shiro 提供了应用程序安全API来执行以下方面(将这些称为应用程序安全的四个基石):
- 身份认证(Authentication) 证明用户身份,相当于用户登录
- 授权(Authorization) 访问控制
- 密码学(Cryptography) 保护或隐藏数据不被窥探
- 会话管理(Session Management) 用户登录后就产生一次会话,退出之前会话中的信息都存在 session 中
Shiro 架构
- Subject:应用代码直接交互的对象是Subject,也就是说Shiro的对外API核心就是Subject,Subject代表当前的用户,这个用户不一定是具体的用户,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等,与Subject的所有交互都会委托给SecurityManager;Subject其实是一个门面,SecurityManager 才是实际的执行者。
- SecurityManager:安全管理器,即所有与安全有关的操作都会与 SercurityManager 交互,并且它管理着所有的 Subject,可以看出它是 Shiro 的核心,它负责与 Shiro 的其他组件进行交互,它相当于 SpringMVC 的 DispatcherServlet 的角色。
- Realm:该对象中配置了用户的认证与授权,SecurityManager从 Realm 对象中获取安全数据(如用户,角色,权限信息)来确定用户的身份是否合法,用户的操作是否能够进行。
整合流程:
大致流程:
- 导入 shiro 启动器依赖。
- 创建 Realm 类,继承抽象类
AuthorizingRealm
,并重写doGetAuthorizationInfo
与doGetAuthenticationInfo
两个方法。 - 创建 shiro 配置类,在配置类中配置中注册三个 bean,三个 bean 分别是自定义的 Realm 对象、SecurityManager 对象、FilterFactoryBean 对象,在注册 FilterFactoryBean 的方法中配置访问 url 所需要的权限,与 Spring Security 中的授权方法类似。
代码结构与案例
案例实现拥有 add 权限的用户能进入 访问add.html,拥有 update 权限的用户能访问 update.html,实现登录功能
流程与代码:
导入 shiro 启动器依赖,当前 Spring Boot 版本为<version>2.2.6.RELEASE</version>
:
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.5.2</version>
</dependency>
配置Realm 类:
public class UserRealm extends AuthorizingRealm {
@Autowired
private UserMapper mapper;
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
Subject currentUser = SecurityUtils.getSubject();
User user = (User) currentUser.getPrincipal();
//获取用户对应权限
info.setStringPermissions(mapper.getUserPermission(user.getRoleId()));
return info;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
//查询是否有这个用户
User user = mapper.login(userToken.getUsername());
if (user == null) {
return null;
}
//user传入session中
Subject currentUser = SecurityUtils.getSubject();
Session session = currentUser.getSession();
session.setAttribute("userName", user.getUserName());
//进行密码认证
return new SimpleAuthenticationInfo(user, user.getUserPassword(), "");
}
}
shiro 配置类:
ShiroFilterFactoryBean 中需要使用 LinkedHashMap 去存储对应的 url 所需的权限信息
@Configuration
public class ShiroConfig {
//ShiroFilterFactoryBean对象
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(securityManager);
/*
* anon:无需认证就可以访问
authc:必须认证了才能访问
user: 必须拥有记住我功能才能用
perms:拥有对某 个资源的权限才能访间;
role:拥有某个角色权限才能访问
* */
Map<String, String> map = new LinkedHashMap<>();
//定义请求是否需要权限
map.put("/user/add", "perms[add]");
map.put("/user/update", "perms[update]");
bean.setFilterChainDefinitionMap(map);
//定义去登录页面的请求
bean.setLoginUrl("/toLoginPage");
//定义未授权的请求
bean.setUnauthorizedUrl("/unAuthorized");
return bean;
}
//DefaultWebSecurityManager
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("getRealm") UserRealm realm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm);
return securityManager;
}
//Realm自定义对象
@Bean
public UserRealm getRealm() {
return new UserRealm();
}
}
写 controller 中的登录方法:
- 用户点击登录后,会先进入 login 方法,将传过来的用户名与密码封装成一个 token 令牌,调用
currentUser.login(token)
方法后会进入 Realm 类中的认证方法,在认证方法内会去数据库查询用户名是否存在,判断密码是否错误,如果出现以上问题则抛出异常,异常被登录方法捕获,返回不同的 msg 错误信息,之后再会去 Realm 授权方法中获取用户的权限信息。
@PostMapping("login")
public String login(String name, String password, Model model) {
Subject currentUser = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(name, password);
try {
currentUser.login(token);
model.addAttribute("msg", "欢迎 " + name + " 登录");
return "index";
} catch (UnknownAccountException uae) {
model.addAttribute("msg", "用户名不存在!");
return "user/login";
} catch (IncorrectCredentialsException ice) {
model.addAttribute("msg", "密码错误!");
return "user/login";
}
}
Shiro 与 Thymeleaf 的整合
仍旧导入整合依赖:
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
在 html 的标签中加入 shiro 命名空间,例如:
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro">
html 标签中用 shiro:hasPermission
进行判断:
<div shiro:hasPermission="add">
<a th:href="@{/user/add}">add</a>
</div>
<div shiro:hasPermission="update">
<a th:href="@{/user/update}">update</a>
</div>
案例 GitHub 链接:Spring Boot Shiro