*******完整代码在文章最下面,转载请说明出处,谢谢 *******
简单的web应用进行身份认证的流程:
1.对未认证的用户请求进行拦截,跳转到认证页面。
2.用户通过用户名+密码及其他凭证进行身份认证,认证成功跳转成功页面,认证失败提示相关失败信息。
根据流程,采用springboot+shiro进行快速开发。
使用到的相关pom依赖:
<!-- web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- thymeleaf 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- shiro相关依赖 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
1.对未认证的用户请求进行拦截,跳转到认证页面。
(0) 这里需要shiro的拦截器配置,新建ShiroConfig配置类,配置过滤器
/**
* @Description springboot中的Shiro配置类
* @Author 张小黑的猫
* @data 2019-05-22 17:17
*/
@Configuration
public class ShiroConfig {
/**
* 配置Shiro的Web过滤器,拦截浏览器请求并交给SecurityManager处理
* @return
*/
@Bean
public ShiroFilterFactoryBean webFilter(){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//配置拦截链 使用LinkedHashMap,因为LinkedHashMap是有序的,shiro会根据添加的顺序进行拦截
// Map<K,V> K指的是拦截的url V值的是该url是否拦截
Map<String,String> filterChainMap = new LinkedHashMap<String,String>(16);
//authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问,先配置anon再配置authc。
filterChainMap.put("/login","anon");
filterChainMap.put("/**", "authc");
//设置拦截请求后跳转的URL.
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap);
return shiroFilterFactoryBean;
}
}
- Shiro的用户认证
先说下shiro认证的流程:
创建SecurityManager
安全管理器 > 主体Subject
提交认证信息 >SecurityManager
安全管理器认证 >SecurityManager
调用Authenticator
认证器认证 >Realm
验证
有几个概念:
- Subject:主体,代表了当前“用户”;所有
Subject
都绑定到SecurityManager
,与Subject
的所有交互都会委托给SecurityManager
;可以把Subject
认为是一个门面;SecurityManager
才是实际的执行者; - SecurityManager安全管理器:所有与安全有关的操作都会与
SecurityManager
交互;且它管理着所有Subject
;负责与后边介绍的其他组件进行交互。(类似于SpringMVC
中的DispatcherServlet
控制器) - Realm:域,Shiro从从
Realm
获取安全数据(如用户、角色、权限),就是说SecurityManager
要验证用户身份,那么它需要从Realm
获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm
得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource
,即安全数据源。
(1)首先在ShiroConfig配置类中创建SecurityManager
安全管理器
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
return securityManager;
}
注意:SecurityManager
导包的时候选org.apache.shiro.mgt.SecurityManager;
而不是java.lang.SecurityManager
(2)SecurityManager
安全管理器需要到realm中去验证认证信息,所以给SecurityManager
设置Realm
。
Shiro的Realm
分为IniRealm
、JdbcRealm
以及自定义realm
,我们这里使用自定义的realm
实现业务逻辑。新建自定义Realm类CustomRealm
继承AuthorizingRealm
。
public class CustomRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
return null;
}
}
(3)重写AuthorizingRealm
中的认证方法doGetAuthenticationInfo
。
@Override
/**
* 认证
*/
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//1.获取用户输入的账号
String username = (String)token.getPrincipal();
//2.通过username从数据库中查找到user实体
User user = getUserByUserName(username);
if(user == null){
return null;
}
//3.通过SimpleAuthenticationInfo做身份处理
SimpleAuthenticationInfo simpleAuthenticationInfo =
new SimpleAuthenticationInfo(user,user.getPassword(),getName());
//4.用户账号状态验证等其他业务操作
if(!user.getAvailable()){
throw new AuthenticationException("该账号已经被禁用");
}
//5.返回身份处理对象
return simpleAuthenticationInfo;
}
上面的token凭证是来自主体Subject.login(token)
提交时的token,主体的提交下面会说到。
这里还有个比较有意思的点:new SimpleAuthenticationInfo(user,user.getPassword(),getName());
中的参数问题。第一个参数是从数据库中获取的User对象
,第二个参数是数据库获取的密码,第三个参数是当前Realm的名称。其中第一个参数传username也可以,那么传username
和User对象
的区别是啥呢?Shiro为我们提供了获取当前用户信息的方法:
* shiro获取当前用户
* @return
*/
private User currentUser(){
User currentUser = (User) SecurityUtils.getSubject().getPrincipal();
return currentUser;
}
有些业务场景需要获取当前用户的信息(用户的状态等等)进行业务操作,那么上面的区别就显而易见了。如果传的是username
那么用户的其他信息就拿不到了。
(4) 将自定义Realm注入到SecurityManager
安全管理器中
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//将自定义的realm交给SecurityManager管理
securityManager.setRealm(new CustomRealm());
return securityManager;
}
(5) Shiro配置类的过滤器中启用安全管理器,即shiroFilterFactoryBean
中配置SecurityManager
//设置securityManager
shiroFilterFactoryBean.setSecurityManager(securityManager());
(6) 主体提交认证信息,即登录请求
@PostMapping("/login")
public String login(String username, String password, Model model){
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
Subject currentUser = SecurityUtils.getSubject();
try {
//主体提交登录请求到SecurityManager
currentUser.login(token);
}catch (IncorrectCredentialsException ice){
model.addAttribute("msg","密码不正确");
}catch(UnknownAccountException uae){
model.addAttribute("msg","账号不存在");
}catch(AuthenticationException ae){
model.addAttribute("msg","状态不正常");
}
if(currentUser.isAuthenticated()){
System.out.println("认证成功");
model.addAttribute("currentUser",currentUser());
return "success";
}else{
token.clear();
return "login";
}
}
到这基本认证的流程就已经通了。在这里贴一下项目结构和全部代码,供大家参考:(不建议直接全部复制粘贴)
pom.xml
:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- shiro相关依赖 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
ShiroConfig.java:
/**
* @Description springboot中的Shiro配置类
* @Author 张小黑的猫
* @data 2019-05-22 17:17
*/
@Configuration
public class ShiroConfig {
/**
* 配置Shiro核心 安全管理器 SecurityManager
* SecurityManager安全管理器:所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;负责与后边介绍的其他组件进行交互。(类似于SpringMVC中的DispatcherServlet控制器)
*/
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//将自定义的realm交给SecurityManager管理
securityManager.setRealm(new CustomRealm());
return securityManager;
}
/**
* 配置Shiro的Web过滤器,拦截浏览器请求并交给SecurityManager处理
* @return
*/
@Bean
public ShiroFilterFactoryBean webFilter(){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//设置securityManager
shiroFilterFactoryBean.setSecurityManager(securityManager());
//配置拦截链 使用LinkedHashMap,因为LinkedHashMap是有序的,shiro会根据添加的顺序进行拦截
// Map<K,V> K指的是拦截的url V值的是该url是否拦截
Map<String,String> filterChainMap = new LinkedHashMap<String,String>(16);
//配置退出过滤器logout,由shiro实现
filterChainMap.put("/logout","logout");
//authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问,先配置anon再配置authc。
filterChainMap.put("/login","anon");
filterChainMap.put("/**", "authc");
//设置默认登录的URL.
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap);
return shiroFilterFactoryBean;
}
}
CustomRealm.java:
/**
* @Description: shiro的自定义realm
* Realm:域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。
* @Author 张小黑的猫
* @data 2019-05-22 17:51
*/
public class CustomRealm extends AuthorizingRealm {
@Override
/**
* 认证
*/
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//1.获取用户输入的账号
String username = (String)token.getPrincipal();
//2.通过username从数据库中查找到user实体
User user = getUserByUserName(username);
if(user == null){
return null;
}
//3.通过SimpleAuthenticationInfo做身份处理
SimpleAuthenticationInfo simpleAuthenticationInfo =
new SimpleAuthenticationInfo(user,user.getPassword(),getName());
//4.用户账号状态验证等其他业务操作
if(!user.getAvailable()){
throw new AuthenticationException("该账号已经被禁用");
}
//5.返回身份处理对象
return simpleAuthenticationInfo;
}
@Override
/**
* 授权
*/
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
return null;
}
/**
* 模拟通过username从数据库中查找到user实体
* @param username
* @return
*/
private User getUserByUserName(String username){
List<User> users = getUsers();
for(User user : users){
if(user.getUsername().equals(username)){
return user;
}
}
return null;
}
/**
* 模拟数据库数据
* @return
*/
private List<User> getUsers(){
List<User> users = new ArrayList<>();
users.add(new User("张小黑的猫","123qwe",true));
users.add(new User("张小黑的狗","123qwe",false));
return users;
}
}
User.java
/**
* @Description 用户
* @Author 张小黑的猫
* @data 2019-05-22 19:18
*/
public class User {
private String username;
private String password;
private Boolean available;
public User(String username, String password, Boolean available) {
this.username = username;
this.password = password;
this.available = available;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Boolean getAvailable() {
return available;
}
public void setAvailable(Boolean available) {
this.available = available;
}
}
LoginController.java:
/**
* @Description 登录
* @Author 张小黑的猫
* @data 2019-05-22 18:17
*/
@Controller
public class LoginController {
@GetMapping("/login")
public String login(){
return "login";
}
@PostMapping("/login")
public String login(String username, String password, Model model){
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
Subject currentUser = SecurityUtils.getSubject();
try {
//主体提交登录请求到SecurityManager
currentUser.login(token);
}catch (IncorrectCredentialsException ice){
model.addAttribute("msg","密码不正确");
}catch(UnknownAccountException uae){
model.addAttribute("msg","账号不存在");
}catch(AuthenticationException ae){
model.addAttribute("msg","状态不正常");
}
if(currentUser.isAuthenticated()){
System.out.println("认证成功");
model.addAttribute("username",username);
return "success";
}else{
token.clear();
return "login";
}
}
}
login.html:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Login</title>
</head>
<body>
<form action="/login" method="post">
<span th:text="${msg}" style="color: red"></span><br>
用户名:<input type="text" name="username"><br>
密 码:<input type="password" name="password"><br>
<input type="submit" value="Login">
</form>
</body>
</html>
success.html:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>success</title>
</head>
<body>
<span th:text="'欢迎你,'+${username}"></span>
</body>
</html>
共同学习,欢迎指正修改~ 喵喵喵❤
下一篇文章:Springboot整合Shiro: 详细的权限管理