分享

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

 鹰兔牛熊眼 2020-05-09

小Hub领读:

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


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

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

集成Shiro

根据官方文档: https://shiro./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. @Bean

  4. AccountRealm accountRealm() {

  5. return new AccountRealm();

  6. }

  7. @Bean

  8. public DefaultWebSecurityManager securityManager(AccountRealm accountRealm) {

  9. DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

  10. securityManager.setRealm(accountRealm);

  11. return securityManager;

  12. }

  13. @Bean

  14. public ShiroFilterChainDefinition shiroFilterChainDefinition() {

  15. DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();

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

  17. chainDefinition.addPathDefinition('/admin/**', 'authc, roles[admin]');

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

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

  20. chainDefinition.addPathDefinition('/login', 'anon');

  21. chainDefinition.addPathDefinition('/doLogin', 'anon');

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

  23. chainDefinition.addPathDefinition('/**', 'authc');

  24. return chainDefinition;

  25. }

  26. }

上面说到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. boolean supports(AuthenticationToken token);

  4. AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;

  5. }

而从上一篇文章中,我们分析的认证授权的源码过程时候,你会看到,认证和授权分别调用的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. @Autowired

  3. UserService userService;

  4. /**

  5. * 授权方法

  6. */

  7. @Override

  8. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

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

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

  11. if(principal.getUsername().equals('MarkerHub')){

  12. SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

  13. info.addRole('admin');

  14. return info;

  15. }

  16. return null;

  17. }

  18. /**

  19. * 认证方法

  20. */

  21. @Override

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

  23. UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;

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

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

  26. SecurityUtils.getSubject().getSession().setAttribute('profile', profile);

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

  28. return info;

  29. }

  30. }

  • com.markerhub.service.impl.UserServiceImpl

  1. @Service

  2. public class UserServiceImpl implements UserService {

  3. @Override

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

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

  6. if(!'MarkerHub'.equals(username)) {

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

  8. throw new UnknownAccountException('用户不存在');

  9. }

  10. if(!'111111'.equals(password)) {

  11. throw new IncorrectCredentialsException('密码错误');

  12. }

  13. AccountProfile profile = new AccountProfile();

  14. profile.setId(1L);

  15. profile.setUsername('MarkerHub');

  16. profile.setSign('欢迎关注公众号MarkerHub哈');

  17. return profile;

  18. }

  19. }

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

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

  • com.markerhub.controller.IndexController

  1. @Controller

  2. public class IndexController {

  3. @Autowired

  4. HttpServletRequest req;

  5. @RequestMapping({'/', '/index'})

  6. public String index() {

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

  8. return 'index';

  9. }

  10. @GetMapping('/login')

  11. public String login() {

  12. return 'login';

  13. }

  14. /**

  15. * 登录

  16. */

  17. @PostMapping('/doLogin')

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

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

  20. try {

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

  22. } catch (AuthenticationException e) {

  23. if (e instanceof UnknownAccountException) {

  24. req.setAttribute('errorMess', '用户不存在');

  25. } else if (e instanceof LockedAccountException) {

  26. req.setAttribute('errorMess', '用户被禁用');

  27. } else if (e instanceof IncorrectCredentialsException) {

  28. req.setAttribute('errorMess', '密码错误');

  29. } else {

  30. req.setAttribute('errorMess', '用户认证失败');

  31. }

  32. return '/login';

  33. }

  34. return 'redirect:/';

  35. }

  36. /**

  37. * 退出登录

  38. */

  39. @GetMapping('/logout')

  40. public String logout() {

  41. SecurityUtils.getSubject().logout();

  42. return 'redirect:/login';

  43. }

  44. }

第六步:登录页面:

  • 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>

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多