个人公众号: MarkerHub,Java网站: https://markerhub.com更多精选文章请点击:Java笔记大全.mdhtml
导入jar包,配置yml参数,编写ShiroConfig定义DefaultWebSecurityManager,重写Realm,编写controller,编写页面,一鼓作气。搞定,是个高手~前端
上面一篇文章中,咱们已经知道了shiro的认证与受权过程,这也是shiro里面最核心经常使用的基础功能。如今咱们把shiro集成到咱们的项目中,开始搭建一个有认证和权限体系的项目,好比用户中心须要登陆以后才能访问等!java
根据官方文档:
https://shiro.apache.org/spring-boot.htmlgithub
第一步:集成导入jar包:web
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-web-starter</artifactId> <version>1.4.2</version> </dependency>
有些同窗还在用shiro-spring
的jar包,可是集成的配置就相对多一点,因此能够直接使用starter包更加方便。redis
第二步:写好配置,官方给咱们提供的属性参数,以及一些默认值,若是不符合咱们的需求,能够自行改动哈。spring
从配置上就能够看出,shiro的注解功能,rememberMe等功能已近自动集成进来了。因此starter包使用起来仍是很是简单的,只须要熟悉shiro的流程,从0开发不在话下哈。apache
shiro: web: enabled: true loginUrl: /login spring: freemarker: suffix: .ftl # 注意新版本后缀是 .ftlh template-loader-path: classpath:/templates/ settings: classic_compatible: true #处理空值
上面的配置,我就改了一下登陆的url,其余都是使用默认的,做为咱们最简单的测试,相信大家。缓存
第三步:配置shiro的securityManager和自定义realm。由于realm负责咱们的认证与受权,因此是必须的,自定义的realm必需要交给securityManager管理,因此这两个类须要重写。而后还有一些资源的权限说明,因此通常须要定义ShiroFilterChainDefinition,因此有3个类咱们常写的:
@Configuration public class ShiroConfig { @Bean AccountRealm accountRealm() { return new AccountRealm(); } @Bean public DefaultWebSecurityManager securityManager(AccountRealm accountRealm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(accountRealm); return securityManager; } @Bean public ShiroFilterChainDefinition shiroFilterChainDefinition() { DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition(); // logged in users with the 'admin' role chainDefinition.addPathDefinition("/admin/**", "authc, roles[admin]"); // logged in users with the 'document:read' permission chainDefinition.addPathDefinition("/docs/**", "authc, perms[document:read]"); chainDefinition.addPathDefinition("/login", "anon"); chainDefinition.addPathDefinition("/doLogin", "anon"); // all other paths require a logged in user chainDefinition.addPathDefinition("/**", "authc"); return chainDefinition; } }
上面说到ShiroFilterChainDefinition是定义过滤器配置的,啥意思呢,咱们来看看其中一句:
chainDefinition.addPathDefinition("/admin/**", "authc, roles[admin]");
这一句代码意思是说:访问/admin/**
开头的连接,都须要已经完成登陆认证authc
、而且拥有admin
角色权限才能访问。
你能够看到key-value是连接-过滤器
的组合,过滤器能够同时多个。那么authc、role、perms、anon究竟是哪来的呢?有啥特殊意义?是啥拦截器?
咱们来看下这个说明文档:
能够看到,其实每一个简写单词,都是一个过滤器的名称。好比authc表明这FormAuthenticationFilter
。每一个过滤器具体是啥用的?咱们看几个经常使用的吧:
第四步:第ok,根据需求项目的资源制定项目过滤器链ShiroFilterChainDefinition
。咱们再回到AccountRealm这个类。咱们以前说过,认证受权的过程,咱们是在Realm里面完成的。因此咱们须要继承Realm,并实现两个方法。
可是这里须要注意,咱们通常不直接继承Realm
,能够看看Realm接口:
public interface Realm { String getName(); boolean supports(AuthenticationToken token); AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException; }
而从上一篇文章中,咱们分析的认证受权的源码过程时候,你会看到,认证和受权分别调用的realm是AuthenticatingRealm
和AuthorizingRealm
。说明源码里面已经通过了一些封装,因此咱们就不能再直接继承Realm
,那么AuthenticatingRealm
和AuthorizingRealm
咱们继承哪一个呢?咱们发现AuthorizingRealm
是继承AuthenticatingRealm
的,因此在重写realm的时候,咱们只须要集成超类AuthorizingRealm
便可。
public abstract class AuthorizingRealm extends AuthenticatingRealm
因此,结合了受权与验证,还有缓存功能,咱们自定义Realm的时候继承AuthorizingRealm便可。
public class AccountRealm extends AuthorizingRealm { @Autowired UserService userService; /** * 受权方法 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { AccountProfile principal = (AccountProfile) principalCollection.getPrimaryPrincipal(); // 硬编码(赋予用户权限或角色) if(principal.getUsername().equals("MarkerHub")){ SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.addRole("admin"); return info; } return null; } /** * 认证方法 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; AccountProfile profile = userService.login(token.getUsername(), String.valueOf(token.getPassword())); // 把用户信息存到session中,方便前端展现 SecurityUtils.getSubject().getSession().setAttribute("profile", profile); SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(profile, token.getCredentials(), getName()); return info; } }
@Service public class UserServiceImpl implements UserService { @Override public AccountProfile login(String username, String password) { //TODO 查库,而后匹配密码是否正确! if(!"MarkerHub".equals(username)) { // 抛出shiro异常,方便通知用户登陆错误信息 throw new UnknownAccountException("用户不存在"); } if(!"111111".equals(password)) { throw new IncorrectCredentialsException("密码错误"); } AccountProfile profile = new AccountProfile(); profile.setId(1L); profile.setUsername("MarkerHub"); profile.setSign("欢迎关注公众号MarkerHub哈"); return profile; } }
上面代码中,我login方法直接给出了帐号MarkerHub,并赋予了角色admin。
第五步:ok,准备动做已经热身完毕,接下来咱们去编写登陆、退出接口,以及咱们的界面:
@Controller public class IndexController { @Autowired HttpServletRequest req; @RequestMapping({"/", "/index"}) public String index() { System.out.println("已登陆,正在访问!!"); return "index"; } @GetMapping("/login") public String login() { return "login"; } /** * 登陆 */ @PostMapping("/doLogin") public String doLogin(String username, String password) { UsernamePasswordToken token = new UsernamePasswordToken(username, password); try { SecurityUtils.getSubject().login(token); } catch (AuthenticationException e) { if (e instanceof UnknownAccountException) { req.setAttribute("errorMess", "用户不存在"); } else if (e instanceof LockedAccountException) { req.setAttribute("errorMess", "用户被禁用"); } else if (e instanceof IncorrectCredentialsException) { req.setAttribute("errorMess", "密码错误"); } else { req.setAttribute("errorMess", "用户认证失败"); } return "/login"; } return "redirect:/"; } /** * 退出登陆 */ @GetMapping("/logout") public String logout() { SecurityUtils.getSubject().logout(); return "redirect:/login"; } }
第六步:登陆页面:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"/> <title>MarkerHub 登陆</title> </head> <body> <h1>用户登陆</h1> <h3>欢迎关注公众号:MarkerHub</h3> <form method="post" action="/doLogin"> username: <input name="username" type="text"> password: <input name="password" type="password"> <input type="submit" name="提交"> </form> <div style="color: red;">${errorMess}</div> </body> </html>
登陆成功页面:
<h1>登陆成功:${profile.username}</h1> <h3>${profile.sign}</h3> <div><a href="/logout">退出</a></div>
ok,代码咱们已经编写完成,接下来,咱们运行项目,而后访问首页,将自行跳转到登陆页面,而后输入帐号密码以后,咱们能够看到完成登陆!
登陆界面:
登陆成功页面:
好了,今天作了一个极简的登陆注册功能,介绍了一下shiro的基本整合步骤。流程仍是挺简单的哈哈,不知道你看懂了没。
而在一些负载均衡的场景中,咱们的会话信息是须要共享的,因此shiro通常会和redis整合在一块儿,你知道怎么整合吗?咱们明天再聊哈,记得来哦,MarkerHub天天发文时间19点20分。
附:demo git源码地址:https://github.com/MarkerHub/...
(完)