本篇文章为大家展示了SpringBoot中怎么利用Shiro实现登陆认证和权限管理,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。
Apache Shiro是一个功能强大、灵活的,开源的安全框架。它可以干净利落地处理身份验证、授权、企业会话管理和加密。
Apache Shiro的首要目标是易于使用和理解。安全通常很复杂,甚至让人感到很痛苦,但是Shiro却不是这样子的。一个好的安全框架应该屏蔽复杂性,向外暴露简单、直观的API,来简化开发人员实现应用程序安全所花费的时间和精力。
验证用户身份
用户访问权限控制,比如:1、判断用户是否分配了一定的安全角色。2、判断用户是否被授予完成某个操作的权限
在非 web 或 EJB 容器的环境下可以任意使用Session API
可以响应认证、访问控制,或者 Session 生命周期中发生的事件
可将一个或以上用户安全数据源数据组合成一个复合的用户 “view”(视图)
支持单点登录(SSO)功能
支持提供“Remember Me”服务,获取用户关联信息而无需登录
等等——都集成到一个有凝聚力的易于使用的API。
Shiro 致力在所有应用环境下实现上述功能,小到命令行应用程序,大到企业应用中,而且不需要借助第三方框架、容器、应用服务器等。当然 Shiro 的目的是尽量的融入到这样的应用环境中去,但也可以在它们之外的任何环境下开箱即用。
Authentication(认证), Authorization(授权), Session Management(会话管理), Cryptography(加密)被 Shiro 框架的开发团队称之为应用安全的四大基石。那么就让我们来看看它们吧:
Authentication(认证):用户身份识别,通常被称为用户“登录”
Authorization(授权):访问控制。比如某个用户是否具有某个操作的使用权限。
Session Management(会话管理):特定于用户的会话管理,甚至在非web 或 EJB 应用程序。
Cryptography(加密):在对数据源使用加密算法加密的同时,保证易于使用。
还有其他的功能来支持和加强这些不同应用环境下安全领域的关注点。特别是对以下的功能支持:
Web支持:Shiro 提供的 web 支持 api ,可以很轻松的保护 web 应用程序的安全。
缓存:缓存是 Apache Shiro 保证安全操作快速、高效的重要手段。
并发:Apache Shiro 支持多线程应用程序的并发特性。
测试:支持单元测试和集成测试,确保代码和预想的一样安全。
“Run As”:这个功能允许用户假设另一个用户的身份(在许可的前提下)。
“Remember Me”:跨 session 记录用户的身份,只有在强制需要时才需要登录。
注意: Shiro不会去维护用户、维护权限,这些需要我们自己去设计/提供,然后通过相应的接口注入给Shiro
User(用户)实体类创建,本例使用了Lombok插件,通过@Data注解减少了对GET/SET方法的生成。
@Data @Entity @Table(name = "user") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; private String username; private String password; private String salt; @Column(name = "is_deleted",length = 2) private int isDeleted; //删除标记 0未删除 1已删除 @Column(name = "gmt_create",updatable = false) private LocalDateTime createAt; @Column(name = "gmt_modified") private LocalDateTime updateAt; @ManyToMany(fetch = FetchType.EAGER) @JoinTable(name = "user_to_role", joinColumns = { @JoinColumn(name = "user_id") }, inverseJoinColumns = { @JoinColumn(name = "role_id") }) private List<SysRole> roles; public String getCredentialsSalt() { return username + salt + salt; } }
用户对应角色的对应关系声明为多对多关联,其中关联表实体可以在源代码中查看,此处忽略。
Role(角色)实体类创建:
@Data @Entity @Table(name = "role") public class SysRole { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; private String role; private String roleName; @Column(name = "is_deleted",length = 2) private int isDeleted; //删除标记 0未删除 1已删除 @CreatedDate @Column(name = "gmt_create") private LocalDateTime createAt; @LastModifiedDate @Column(name = "gmt_modified") private LocalDateTime updateAt; @ManyToMany(fetch = FetchType.EAGER) @JoinTable(name = "role_to_permission", joinColumns = { @JoinColumn(name = "role_id") }, inverseJoinColumns = { @JoinColumn(name = "perm_id") }) private List<SysPermission> permissions; }
Permission(权限)实体类创建:
@Data @Entity @Table(name = "permission") public class SysPermission { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String code; private String name; @Column(name = "is_deleted",length = 2) private int isDeleted; //删除标记 0未删除 1已删除 @CreatedDate @Column(name = "gmt_create") private LocalDateTime createAt; @LastModifiedDate @Column(name = "gmt_modified") private LocalDateTime updateAt; }
本例使用JPA作为数据库持久层框架,因此我们将建表的任务交给框架自动完成,我们只需要在entity中写清楚对应关系即可。源码所给的struts.sql文件也详细说明了DDL语句。
Shiro的重点在于对应的config文件的配置以及对于Realm的实现。
ShiroConfig的实现方式,在Shiro中,shirFilter会对配置的路径进行过滤,过滤的级别如下:
/** * shiro的内置过滤器 * anon:无需认证就可以访问 默认 * authc:必须认证了才能访问 * user:必须拥有记住我功能才能访问 * perms:必须拥有对某个的权限才能访问 * role:拥有某个角色权限才能访问 * ilter工厂,设置对应的过滤条件和跳转条件 */
@Configuration public class ShiroConfig { @Bean public ShiroFilterFactoryBean shirFilter(DefaultWebSecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); Map<String, String> filterChainDefinitionMap = new HashMap<>(); shiroFilterFactoryBean.setLoginUrl("/login"); shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized"); shiroFilterFactoryBean.setSuccessUrl("/home/index"); filterChainDefinitionMap.put("/*", "anon"); filterChainDefinitionMap.put("/authorized/index", "authc"); filterChainDefinitionMap.put("/authorized/admin", "roles[admin]"); filterChainDefinitionMap.put("/authorized/add", "perms[Create,Update]"); filterChainDefinitionMap.put("/authorized/delete", "perms[Delete]"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } @Bean public HashedCredentialsMatcher hashedCredentialsMatcher() { HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); hashedCredentialsMatcher.setHashAlgorithmName(PasswordHelper.ALGORITHM_NAME); // 散列算法 hashedCredentialsMatcher.setHashIterations(PasswordHelper.HASH_ITERATIONS); // 散列次数 return hashedCredentialsMatcher; } //注入自己的realm @Bean public CustomizedRealm shiroRealm() { CustomizedRealm shiroRealm = new CustomizedRealm(); shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher()); //设置加密的方式 return shiroRealm; } @Bean public DefaultWebSecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(shiroRealm()); return securityManager; } @Bean public PasswordHelper passwordHelper() { return new PasswordHelper(); } //加入注解的使用,不加入这个注解不生效 @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } }
自定义Realm的实现:
public class CustomizedRealm extends AuthorizingRealm{ @Autowired private UserService userService; /** * 授权 * 只有当需要检测用户权限的时候才会调用此方法,例如checkRole,checkPermission之类的 * @param principals * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); String username = (String) principals.getPrimaryPrincipal(); User user = userService.findUserByName(username); if(user == null) return null; for (SysRole role : user.getRoles()) { authorizationInfo.addRole(role.getRole()); for (SysPermission permission : role.getPermissions()) { authorizationInfo.addStringPermission(permission.getName()); } } return authorizationInfo; } /** * 认证 * 默认使用此方法进行用户名正确与否验证,错误抛出异常即可。 * @param token * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String username = (String) token.getPrincipal(); User user = userService.findUserByName(username); if(user == null) throw new AuthenticationException(ErrorCode.USER_NOT_FOUND.getMsg()); SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), ByteSource.Util.bytes(user.getCredentialsSalt()), getName()); return authenticationInfo; } }
简单的控制层逻辑:
@RestController @RequestMapping @Slf4j public class UserController { @Autowired private UserService userService; @Autowired private PasswordHelper passwordHelper; @GetMapping("doLogin") public String login(@RequestParam String username, @RequestParam String password) { UsernamePasswordToken token = new UsernamePasswordToken(username, password); Subject subject = SecurityUtils.getSubject(); try { subject.login(token); } catch (IncorrectCredentialsException ice) { throw new AuthorizationException(ErrorCode.USER_PASSWORD_ERROR.getMsg()); } catch (UnknownAccountException uae) { throw new AuthorizationException(ErrorCode.IDENTIFICATION_ERROR.getMsg()); } //登陆成功,将用户信息放入session中 User user = userService.findUserByName(username); subject.getSession().setAttribute("currentUser", user); return "LOGIN SUCCEED"; } @GetMapping("register") public String register(@RequestParam String username, @RequestParam String password) { User user = new User(); user.setUsername(username); user.setPassword(password); passwordHelper.encryptPassword(user); userService.saveUser(user); return "SUCCESS"; } @GetMapping("test") @RequiresRoles(value = {"USER"}) //需要USER角色才能访问 @RequiresPermissions(value = {"QUERY","ADD"},logical = Logical.OR) //多个权限的写法 public String test(@CurrentUser User user) { //@CurrentUser 自定义注解实现,可以参考CurrentUser.java,其实现过程在CurrentUserMethodArgumentResolver类中 log.info("{}",user.getRoles()); return user.getUsername(); } }
上面的例子中,使用@RequiresRoles的注解,需要在ShiroConfig配置AuthorizationAttributeSourceAdvisor,否则注解无法生效。
1.我们创建了有个用户,张三,其拥有User的权限,先通过登录;(没有用户的可以调用register方法先创建用户,再配置对应的权限关联关系)
2.此处编写了有个test方法,一开始我们设置需要user权限可以访问成功,如下:
@GetMapping("test") @RequiresRoles(value = {"USER"}) //需要USER角色才能访问 @RequiresPermissions(value = {"QUERY","ADD"},logical = Logical.OR) //多个权限的写法 public String test(@CurrentUser User user) { //@CurrentUser 自定义注解实现,可以参考CurrentUser.java,其实现过程在CurrentUserMethodArgumentResolver类中 log.info("{}",user.getRoles()); return user.getUsername(); } //@RequiresRoles(value = {"USER"}) zhangsan有用user权限,登陆成功
权限没有问题。
3.修改此处登录的权限访问为,ADMIN级别
@GetMapping("test") @RequiresRoles(value = {"ADMIN"}) //需要ADMIN角色才能访问 @RequiresPermissions(value = {"QUERY","ADD"},logical = Logical.OR) //多个权限的写法 public String test(@CurrentUser User user) { log.info("{}",user.getRoles()); return user.getUsername(); } //@RequiresRoles(value = {"ADMIN"}) zhangsan未拥有ADMIN权限,被拦截
上述内容就是SpringBoot中怎么利用Shiro实现登陆认证和权限管理,你们学到知识或技能了吗?如果还想学到更多技能或者丰富自己的知识储备,欢迎关注亿速云行业资讯频道。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。