只需要6个步骤,springboot集成shiro,并完成登录

小Hub领读:

导入jar包,配置yml参数,编写ShiroConfig定义DefaultWebSecurityManager,重写Realm,编写controller,编写页面,一气呵成。搞定,是个高手~


上面一篇文章中,我们已经知道了shiro的认证与授权过程,这也是shiro里面最核心常用的基础功能。现在我们把shiro集成到我们的项目中,开始搭建一个有认证和权限体系的项目,比如用户中心需要登录之后才能访问等!

1、极简入门,Shiro的认证与授权流程解析

集成Shiro

根据官方文档: https://shiro.apache.org/spring-boot.html

第一步:集成导入jar包:

  1. <dependency>

  2. <groupId>org.apache.shiro</groupId>

  3. <artifactId>shiro-spring-boot-web-starter</artifactId>

  4. <version>1.4.2</version>

  5. </dependency>

有些同学还在用 shiro-spring的jar包,但是集成的配置就相对多一点,所以可以直接使用starter包更加方便。

第二步:写好配置,官方给我们提供的属性参数,以及一些默认值,如果不符合我们的需求,可以自行改动哈。

图片

从配置上就可以看出,shiro的注解功能,rememberMe等功能已近自动集成进来了。所以starter包使用起来还是非常简单的,只需要熟悉shiro的流程,从0开发不在话下哈。

  • application.yml

  1. shiro:

  2. web:

  3. enabled: true

  4. loginUrl: /login

  5. spring:

  6. freemarker:

  7. suffix: .ftl # 注意新版本后缀是 .ftlh

  8. template-loader-path: classpath:/templates/

  9. settings:

  10. classic_compatible: true #处理空值

上面的配置,我就改了一下登录的url,其他都是使用默认的,作为我们最简单的测试,相信你们。

第三步:配置shiro的securityManager和自定义realm。因为realm负责我们的认证与授权,所以是必须的,自定义的realm必须要交给securityManager管理,所以这两个类需要重写。然后还有一些资源的权限说明,所以一般需要定义ShiroFilterChainDefinition,所以有3个类我们常写的:

  • AuthorizingRealm

  • DefaultWebSecurityManager shiro的核心管理器

  • ShiroFilterChainDefinition 过滤器链配置

  1. @Configuration

  2. public class ShiroConfig {

  3.  

  4. @Bean

  5. AccountRealm accountRealm() {

  6. return new AccountRealm();

  7. }

  8.  

  9. @Bean

  10. public DefaultWebSecurityManager securityManager(AccountRealm accountRealm) {

  11. DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

  12. securityManager.setRealm(accountRealm);

  13. return securityManager;

  14. }

  15.  

  16. @Bean

  17. public ShiroFilterChainDefinition shiroFilterChainDefinition() {

  18. DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();

  19.  

  20. // logged in users with the 'admin' role

  21. chainDefinition.addPathDefinition("/admin/**", "authc, roles[admin]");

  22.  

  23. // logged in users with the 'document:read' permission

  24. chainDefinition.addPathDefinition("/docs/**", "authc, perms[document:read]");

  25.  

  26. chainDefinition.addPathDefinition("/login", "anon");

  27. chainDefinition.addPathDefinition("/doLogin", "anon");

  28.  

  29. // all other paths require a logged in user

  30. chainDefinition.addPathDefinition("/**", "authc");

  31. return chainDefinition;

  32. }

  33. }

上面说到ShiroFilterChainDefinition是定义过滤器配置的,啥意思呢,我们来看看其中一句:

  1. chainDefinition.addPathDefinition("/admin/**", "authc, roles[admin]");

这一句代码意思是说:访问 /admin/**开头的链接,都需要已经完成登录认证 authc、并且拥有 admin角色权限才能访问。

你可以看到key-value是 链接-过滤器的组合,过滤器可以同时多个。那么authc、role、perms、anon到底是哪来的呢?有啥特殊意义?是啥拦截器?

我们来看下这个说明文档:

图片

可以看到,其实每个简写单词,都是一个过滤器的名称。比如authc代表这 FormAuthenticationFilter。每个过滤器具体是啥用的?我们看几个常用的吧:

  • authc 基于表单的拦截器,没有登录会跳到相应的登录页面登录

  • user 用户拦截器,用户已经身份验证 / 记住我登录的都可

  • anon 匿名拦截器,即不需要登录即可访问

  • roles 角色授权拦截器,验证用户是否拥有所有角色

  • perms 权限授权拦截器,验证用户是否拥有所有权限

第四步:ok,根据需求项目的资源制定项目过滤器链 ShiroFilterChainDefinition。我们再回到AccountRealm这个类。我们之前说过,认证授权的过程,我们是在Realm里面完成的。所以我们需要继承Realm,并实现两个方法。

但是这里需要注意,我们一般不直接继承 Realm,可以看看Realm接口:

  • org.apache.shiro.realm.Realm

  1. public interface Realm {

  2. String getName();

  3.  

  4. boolean supports(AuthenticationToken token);

  5.  

  6. AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;

  7.  

  8. }

而从上一篇文章中,我们分析的认证授权的源码过程时候,你会看到,认证和授权分别调用的realm是 AuthenticatingRealm和 AuthorizingRealm。说明源码里面已经经过了一些封装,所以我们就不能再直接继承 Realm,那么 AuthenticatingRealm和 AuthorizingRealm我们继承哪个呢?我们发现 AuthorizingRealm是继承 AuthenticatingRealm的,所以在重写realm的时候,我们只需要集成超类 AuthorizingRealm即可。

  1. public abstract class AuthorizingRealm extends AuthenticatingRealm

图片

所以,结合了授权与验证,还有缓存功能,我们自定义Realm的时候继承AuthorizingRealm即可。

  • com.markerhub.shiro.AccountRealm

  1. public class AccountRealm extends AuthorizingRealm {

  2.  

  3. @Autowired

  4. UserService userService;

  5.  

  6. /**

  7. * 授权方法

  8. */

  9. @Override

  10. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

  11.  

  12. AccountProfile principal = (AccountProfile) principalCollection.getPrimaryPrincipal();

  13.  

  14. // 硬编码(赋予用户权限或角色)

  15. if(principal.getUsername().equals("MarkerHub")){

  16. SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

  17. info.addRole("admin");

  18. return info;

  19. }

  20.  

  21. return null;

  22. }

  23.  

  24. /**

  25. * 认证方法

  26. */

  27. @Override

  28. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

  29.  

  30. UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;

  31. AccountProfile profile = userService.login(token.getUsername(), String.valueOf(token.getPassword()));

  32. // 把用户信息存到session中,方便前端展示

  33. SecurityUtils.getSubject().getSession().setAttribute("profile", profile);

  34.  

  35. SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(profile, token.getCredentials(), getName());

  36. return info;

  37. }

  38. }

  • com.markerhub.service.impl.UserServiceImpl

  1. @Service

  2. public class UserServiceImpl implements UserService {

  3.  

  4. @Override

  5. public AccountProfile login(String username, String password) {

  6.  

  7. //TODO 查库,然后匹配密码是否正确!

  8.  

  9. if(!"MarkerHub".equals(username)) {

  10. // 抛出shiro异常,方便通知用户登录错误信息

  11. throw new UnknownAccountException("用户不存在");

  12. }

  13. if(!"111111".equals(password)) {

  14. throw new IncorrectCredentialsException("密码错误");

  15. }

  16.  

  17. AccountProfile profile = new AccountProfile();

  18. profile.setId(1L);

  19. profile.setUsername("MarkerHub");

  20. profile.setSign("欢迎关注公众号MarkerHub哈");

  21.  

  22. return profile;

  23. }

  24. }

上面代码中,我login方法直接给出了账号MarkerHub,并赋予了角色admin。

第五步:ok,准备动作已经热身完毕,接下来我们去编写登录、退出接口,以及我们的界面:

  • com.markerhub.controller.IndexController

  1. @Controller

  2. public class IndexController {

  3.  

  4. @Autowired

  5. HttpServletRequest req;

  6.  

  7. @RequestMapping({"/", "/index"})

  8. public String index() {

  9. System.out.println("已登录,正在访问!!");

  10. return "index";

  11. }

  12.  

  13. @GetMapping("/login")

  14. public String login() {

  15. return "login";

  16. }

  17.  

  18. /**

  19. * 登录

  20. */

  21. @PostMapping("/doLogin")

  22. public String doLogin(String username, String password) {

  23.  

  24. UsernamePasswordToken token = new UsernamePasswordToken(username, password);

  25. try {

  26. SecurityUtils.getSubject().login(token);

  27.  

  28. } catch (AuthenticationException e) {

  29. if (e instanceof UnknownAccountException) {

  30. req.setAttribute("errorMess", "用户不存在");

  31. } else if (e instanceof LockedAccountException) {

  32. req.setAttribute("errorMess", "用户被禁用");

  33. } else if (e instanceof IncorrectCredentialsException) {

  34. req.setAttribute("errorMess", "密码错误");

  35. } else {

  36. req.setAttribute("errorMess", "用户认证失败");

  37. }

  38. return "/login";

  39. }

  40. return "redirect:/";

  41. }

  42.  

  43.  

  44. /**

  45. * 退出登录

  46. */

  47. @GetMapping("/logout")

  48. public String logout() {

  49. SecurityUtils.getSubject().logout();

  50. return "redirect:/login";

  51. }

  52.  

  53. }

第六步:登录页面:

  • templates/login.ftl

  1. <!DOCTYPE html>

  2. <html lang="en">

  3. <head>

  4. <meta charset="utf-8"/>

  5. <title>MarkerHub 登录</title>

  6. </head>

  7. <body>

  8. <h1>用户登录</h1>

  9. <h3>欢迎关注公众号:MarkerHub</h3>

  10.  

  11. <form method="post" action="/doLogin">

  12. username: <input name="username" type="text">

  13. password: <input name="password" type="password">

  14. <input type="submit" name="提交">

  15. </form>

  16.  

  17. <div style="color: red;">${errorMess}</div>

  18. </body>

  19. </html>

登录成功页面:

  • templates/index.ftl

  1. <h1>登录成功:${profile.username}</h1>

  2. <h3>${profile.sign}</h3>

  3.  

  4. <div><a href="/logout">退出</a></div>

ok,代码我们已经编写完成,接下来,我们运行项目,然后访问首页,将自行跳转到登录页面,然后输入账号密码之后,我们可以看到完成登录!

登录界面: 

图片

登录成功页面:

 图片

结束语

好了,今天做了一个极简的登录注册功能,介绍了一下shiro的基本整合步骤。流程还是挺简单的哈哈,不知道你看懂了没。

而在一些负载均衡的场景中,我们的会话信息是需要共享的,所以shiro一般会和redis整合在一起,你知道怎么整合吗?我们明天再聊哈,记得来哦,MarkerHub每天发文时间19点20分。

附:demo git源码地址:https://github.com/MarkerHub/JavaDemo/tree/master/整合示例/springboot-shiro-demo

posted @ 2022-05-12 16:02  知为谁开  阅读(230)  评论(0)    收藏  举报