前言
大家好,一直以来我都本着用最通俗的话理解核心的知识点, 我认为所有的难点都离不开 基础知识 的铺垫。目前正在出一个SpringBoot长期系列教程,从入门到进阶, 篇幅会较多~
适合人群
- 学完Java基础
- 想通过Java快速构建web应用程序
- 想学习或了解SpringBoot
- SpringBoot进阶学习
大佬可以绕过 ~
背景
如果你是一路看过来的,很高兴你能够耐心看完。之前带大家学了Springboot基础部分,对基本的使用有了初步的认识, 接下来的几期内容将会带大家进阶使用,会先讲解基础中间件的使用和一些场景的应用,或许这些技术你听说过,没看过也没关系,我会带大家一步一步的入门,耐心看完你一定会有收获~
情景回顾
上期带大家学习了Shiro安全框架的基本概念和工作原理, 相信有了基本的认识之后,下面学习将会容易些,本期将带大家学习整合Shiro框架和基本使用。同样的,我们集成到Springboot中。
往期内容
项目源码(持续更新 欢迎star⭐️)
环境搭建
我们先修改pom.xml, 引入相关依赖, 为了后期方便学习,我对项目结构做了一些调整,在目录下新建一个模块用于Shiro学习, 具体大家可以查看源码:
<!-- shiro-spring -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
配置 Shiro
今天带大家实现一个用户认证的功能, 我们先添加配置ShiroConfig:
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 设置securityManager
shiroFilterFactoryBean.setSecurityManager((SecurityManager) securityManager);
// 登录的url
shiroFilterFactoryBean.setLoginUrl("/login");
// 登录成功后跳转的url
shiroFilterFactoryBean.setSuccessUrl("/index");
// 未授权url
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// 定义filterChain,静态资源不拦截
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/fonts/**", "anon");
filterChainDefinitionMap.put("/img/**", "anon");
// druid数据源监控页面不拦截
filterChainDefinitionMap.put("/druid/**", "anon");
// 配置退出过滤器,其中具体的退出代码Shiro已经替我们实现了
filterChainDefinitionMap.put("/logout", "logout");
filterChainDefinitionMap.put("/", "anon");
// 除上以外所有url都必须认证通过才可以访问,未通过认证自动访问LoginUrl
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public DefaultWebSecurityManager securityManager(){
// 配置SecurityManager,并注入shiroRealm
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(shiroRealm());
return securityManager;
}
/**
* 自定义实现的Realm
* @return
*/
@Bean
public CustomRealm shiroRealm(){
CustomRealm shiroRealm = new CustomRealm();
return shiroRealm;
}
}
简单说下, anon代表的是允许资源公开访问, authc代表的是资源需要通过认证后才能访问。其实大部分都已经贴在注释上了,大家跟着配就好,重点关注的是shiroRealm()这个方法, 里边有一个类需要我们自行实现,下边我就带大家实现一下:
public class CustomRealm extends AuthorizingRealm {
/**
* 获取用户角色和权限
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
return null;
}
/**
* 登录认证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 获取用户输入的用户名和密码
String userName = (String) token.getPrincipal();
String password = new String((char[]) token.getCredentials());
System.out.println("用户" + userName + "认证-----ShiroRealm.doGetAuthenticationInfo");
// 通过用户名到数据库查询用户信息
User user = UserMock.getUserByUsername(userName);
if (user == null) {
throw new UnknownAccountException("用户名或密码错误!");
}
if (!password.equals(user.getPassword())) {
throw new IncorrectCredentialsException("用户名或密码错误!");
}
if (user.getState().equals("0")) {
throw new LockedAccountException("账号已被锁定,请联系管理员!");
}
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, getName());
return info;
}
}
这里为了方便演示,我直接写死到对象里,使用测试数据来模拟数据库查询到的用户信息,真实业务中是要去数据库查询用户信息的:
@Data
public class User {
public Integer id;
public String username;
public String password;
public String role;
public String state;
}
定义一个 UserMock 类
public class UserMock {
public static String getPassword(String username) {
System.out.println("用户名: " + username);
if(username.equals("admin")) {
return "123";
}else {
return "456";
}
}
public static String getRole(String username) {
if(username.equals("admin")) {
return "admin";
}else {
return "user";
}
}
public static User getUserByUsername(String username) {
User user = new User();
if(username.equals("admin")) {
user.setUsername("admin");
user.setPassword("123");
user.setRole("admin");
}else {
user.setUsername("admin1");
user.setPassword("456");
user.setRole("user");
}
user.setState("1");
return user;
}
}
定义一个首页控制器:
@RestController
public class IndexController {
@RequestMapping("/index")
public String index(Model model) {
// 登录成后,即可通过Subject获取登录的用户信息
User user = (User) SecurityUtils.getSubject().getPrincipal();
model.addAttribute("user", user);
return "index";
}
}
定义一个登录控制器:
@RestController
public class LoginController {
@GetMapping("/login")
public String login() {
return "login";
}
@PostMapping("/login")
@ResponseBody
public String login(String username, String password) {
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
// 获取Subject对象
Subject subject = SecurityUtils.getSubject();
try {
// 执行登录
subject.login(token);
return "ok";
} catch (UnknownAccountException e) {
return e.getMessage();
} catch (IncorrectCredentialsException e) {
return "IncorrectCredentialsException " + e.getMessage();
} catch (LockedAccountException e) {
return "LockedAccountException " + e.getMessage();
} catch (AuthenticationException e) {
return "认证失败!";
}
}
}
运行项目,访问: http://localhost:8878/api/index, 发现没法直接访问自动跳转到了http://localhost:8878/api/login;jsessionid=F7E2EABECB7D87B89EA99C328B344B05,说明我们还未登录, 下面进行登录 POST http://localhost:8878/api/login, 登录成功后会自动跳转/index, 并且返回结果 index --->admin, 说明我们之前的配置都生效了,并且可以通过Subject拿到用户信息
结束语
本期内容就到这里结束了, 总结一下主要讲了在SpringBoot中如何集成Shiro,以及带大家学习了它的基本配置,最后我们完成了一个用户认证的功能。
下期预告
细心的同学可能会发现,当我们浏览器关闭在进来访问首页index的时候,发现又跳转回了登录,这样的交互肯定是不友好的,所以下期带大家学习如何在Shiro中存储我们的Cookie,让它记住我们的信息
***关注公众号加群一起学习交流,关注我,不迷路~ ***