通过Shiro的认证与授权可以搭建一个有认证和权限体系的项目,用户需要登录之后才能访问应用服务。
官方文档: https://shiro.apache.org/spring-boot.html
1.集成导入jar包
org.apache.shiro shiro-spring-boot-web-starter 1.4.2
官方提供了属性参数以及一些默认值。
2.修改配置文件application.yml
shiro: web: enabled: trueloginUrl: /loginspring: freemarker: suffix: .ftl template-loader-path: classpath: /templates/settings: classic_compatible: true
3.配置shiro的securityManager和自定义realm。
realm负责认证与授权,自定义realm由securityManager管理,所以这两个类需要重写。
然后还有一些资源的权限说明,所以一般需要定义ShiroFilterChainDefinition,常用的类有:
- AuthorizingRealm
- DefaultWebSecurityManager shiro的核心管理器
- ShiroFilterChainDefinition 过滤器链配置
@Configurationpublic 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(); chainDefinition.addPathDefinition("/admin/**", "authc, roles[admin]"); chainDefinition.addPathDefinition("/docs/**", "authc, perms[document:read]"); chainDefinition.addPathDefinition("/login", "anon"); chainDefinition.addPathDefinition("/doLogin", "anon"); chainDefinition.addPathDefinition("/**", "authc"); return chainDefinition; }}
ShiroFilterChainDefinition是定义过滤器配置的:
chainDefinition.addPathDefinition("/admin/**", "authc, roles[admin]");
访问 /admin/**开头的链接,都需要已经完成登录认证 authc、并且拥有 admin角色权限才能访问。
说明文档:
可以看到每个简写单词都是一个过滤器的名称。比如authc代表着 FormAuthenticationFilter,常用的过滤器有:
- authc 基于表单的拦截器,没有登录会跳到相应的登录页面登录
- user 用户拦截器,用户已经身份验证 / 记住我登录的都可
- anon 匿名拦截器,即不需要登录即可访问
- roles 角色授权拦截器,验证用户是否拥有所有角色
- perms 权限授权拦截器,验证用户是否拥有所有权限
继承超类 AuthorizingRealm
public abstract class AuthorizingRealm extends AuthenticatingRealm
结合了授权与验证,还有缓存功能,我们自定义Realm的时候继承AuthorizingRealm即可。
- com.markerhub.shiro.AccountRealm
public class AccountRealm extends AuthorizingRealm { @Autowired UserService userService; /** * 授权方法 */ @Override protected AuthorizationInfo doGetAuthorizationInfo( PrincipalCollection principalCollection) { AccountProfile principal = (AccountProfile) principalCollection.getPrimaryPrincipal(); // 硬编码(赋予用户权限或角色) if (principal.getUsername().equals("Test")) { 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())); SecurityUtils.getSubject().getSession().setAttribute("profile", profile); SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(profile, token.getCredentials(), getName()); return info; }}
@Servicepublic class UserServiceImpl implements UserService{ @Override public AccountProfile login(String username, String password) { //TODO 查库,然后匹配密码是否正确! if (!"MarkerHub".equals(username)) { // 抛出shiro异常,方便通知用户登录错误信息 throw new UnknownAccountException("用户不存在"); } if (!"string".equals(password)) { throw new IncorrectCredentialsException("密码错误"); } AccountProfile profile = new AccountProfile(); profile.setId(1L); profile.setUsername("Winson"); profile.setSign("Winson Sign"); return profile; }}
5.增加控制器IndexController
@Controllerpublic 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"; }}@Controllerpublic 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"; }}@Controllerpublic 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"; }}@Controllerpublic 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"; }}
6.增加登录页面templates/login.ftl
MarkerHub 登录
用户登录
欢迎关注公众号:MarkerHub
username: password:
${errorMess}
登录成功页面:
- templates/index.ftl
Title
登录成功:${profile.username}
${profile.sign}
退出
Done 。