wordpress建站方法,免费网站建设绑定域名,网站建设开发方式包括哪些,自己造网站简介
Shiro安全框架是Apache提供的一个强大灵活的安全框架Shiro安全框架提供了认证、授权、企业会话管理、加密、缓存管理相关的功能#xff0c;使用Shiro可以非常方便的完成项目的权限管理模块开发
Shiro的整体架构 1、Subject Subject即主体#xff08;可以把当前用户…简介
Shiro安全框架是Apache提供的一个强大灵活的安全框架Shiro安全框架提供了认证、授权、企业会话管理、加密、缓存管理相关的功能使用Shiro可以非常方便的完成项目的权限管理模块开发
Shiro的整体架构 1、Subject Subject即主体可以把当前用户理解为主体外部应用与Subject进行交互Subject记录了当前操作用户将用户的概念理解为当前操作的主体可能是一个通过浏览器请求的用户也可能是一个运行的程序。Subject在Shiro中是一个接口接口中定义了很多认证授权相关的方法外部程序通过Subject进行认证授而Subject是通过SecurityManager安全管理器进行认证授权
2、Security Manager SecurityManager即安全管理器对全部的Subject进行安全管理它是Shiro的核心负责对所有的Subject进行安全管理。通过SecurityManager可以完成Subject的认证、授权等实质上SecurityManager是通过Authenticator进行认证通过Authorizer进行授权通过SessionManager进行会话管理等。
3、Cryptography Cryptography即密码管理Shiro提供了一套加密/解密的组件方便开发。比如提供常用的散列、加/解密等功能。
4、Authenticator Authenticator即认证器对用户身份进行认证Authenticator是一个接口Shiro提供ModularRealmAuthenticator实现类通过ModularRealmAuthenticator基本上可以满足大多数需求也可以自定义认证器。
5、Authorizer Authorizer即授权器用户通过认证器认证通过在访问功能时需要通过授权器判断用户是否有此功能的操作权限。
6、realm Realm即领域相当于datasource数据源securityManager进行安全认证需要通过Realm获取用户权限数据比如如果用户身份数据在数据库那么realm就需要从数据库获取用户身份信息。
7、sessionManager sessionManager即会话管理Shiro框架定义了一套会话管理它不依赖web容器的session所以Shiro可以使用在非web应用上也可以将分布式应用的会话集中在一点管理此特性可使它实现单点登录。
8、SessionDAO SessionDAO即会话dao是对session会话操作的一套接口比如要将session存储到数据库可以通过jdbc将会话存储到数据库。
9、CacheManager CacheManager即缓存管理将用户权限数据存储在缓存这样可以提高性能。
shiro 使用
1.、shiro的配置类
/*** shiro权限管理的配置*/
Configuration
public class ShiroConfig {/*** 安全管理器*/Beanpublic DefaultWebSecurityManager securityManager(ShiroDatabaseRealm shiroDatabaseRealm,RememberMeManager rememberMeManager,CacheManager cacheManager,SessionManager sessionManager) {DefaultWebSecurityManager securityManager new DefaultWebSecurityManager();securityManager.setRealm(shiroDatabaseRealm);securityManager.setCacheManager(cacheManager);securityManager.setRememberMeManager(rememberMeManager);securityManager.setSessionManager(sessionManager);return securityManager;}/*** 会话管理器*/Beanpublic SessionManager sessionManager() {return new ServletContainerSessionManager();}/*** 缓存管理器*/Beanpublic CacheManager getCacheShiroManager(EhCacheManagerFactoryBean ehcache) {EhCacheManager ehCacheManager new EhCacheManager();ehCacheManager.setCacheManager(ehcache.getObject());return ehCacheManager;}/*** rememberMe管理器*/Beanpublic CookieRememberMeManager rememberMeManager(SimpleCookie rememberMeCookie) {CookieRememberMeManager manager new CookieRememberMeManager();manager.setCipherKey(Base64.decode(Z3VucwAAAAAAAAAAAAAAAA));manager.setCookie(rememberMeCookie);return manager;}/*** 记住密码Cookie*/Beanpublic SimpleCookie rememberMeCookie() {SimpleCookie simpleCookie new SimpleCookie(rememberMe);simpleCookie.setHttpOnly(true);simpleCookie.setMaxAge(7 * 24 * 60 * 60);//7天return simpleCookie;}/*** Shiro的过滤器链*/Beanpublic ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {ShiroFilterFactoryBean shiroFilter new ShiroFilterFactoryBean();shiroFilter.setSecurityManager(securityManager);/*** 默认的登陆访问url*/shiroFilter.setLoginUrl(/login);/*** 登陆成功后跳转的url*/shiroFilter.setSuccessUrl(/);/*** 没有权限跳转的url*/shiroFilter.setUnauthorizedUrl(/global/error);/*** 自定义过滤器*/HashMapString, Filter myFilters new HashMap();myFilters.put(user, new ShiroUserFilter());shiroFilter.setFilters(myFilters);/*** 配置shiro拦截器链** anon 不需要认证* authc 需要认证* user 验证通过或RememberMe登录的都可以** 当应用开启了rememberMe时,用户下次访问时可以是一个user,但不会是authc,因为authc是需要重新认证的** 顺序从上到下,优先级依次降低** api开头的接口走rest api鉴权不走shiro鉴权**/MapString, String hashMap new LinkedHashMap();//NONE_PERMISSION_RES是一个集合里边存储了所有不需要过滤的路径包括登录路径错误路径等将里边所有路径标志为anon即访问不需要权限。for (String nonePermissionRe : NONE_PERMISSION_RES) {hashMap.put(nonePermissionRe, anon);}// 剩下的路径 走自定义过滤器hashMap.put(/**, user);shiroFilter.setFilterChainDefinitionMap(hashMap);return shiroFilter;}/*** Shiro生命周期处理器:* 用于在实现了Initializable接口的Shiro bean初始化时调用Initializable接口回调(例如:UserRealm)* 在实现了Destroyable接口的Shiro bean销毁时调用 Destroyable接口回调(例如:DefaultSecurityManager)*/Beanpublic LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {return new LifecycleBeanPostProcessor();}/*** 启用shrio授权注解拦截方式AOP式方法级权限检查*/BeanDependsOn(lifecycleBeanPostProcessor)public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor new AuthorizationAttributeSourceAdvisor();authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);return authorizationAttributeSourceAdvisor;}
}
2.在login controller中进行登录操作
在login controller中需要使用一个UsernamePasswordToken类型的token来存储用户名、密码、和remeberme token相当于一个令牌。 SecurityUtils.getSubject()得到subject subject是一个非常重要的对象包含了登录、注销、获取当前用户、检查角色、权限等操作的方法。 调用subject.login()方法将token作为参数会进入Shiro的安全管理器并调用 Realm 中的 doGetAuthenticationInfo 方法。 RequestMapping(value /login, method RequestMethod.POST)public String loginVali(String username, String password, String remember) {Subject subject SecurityUtils.getSubject();// 1) TODO 准备 Token 数据UsernamePasswordToken token new UsernamePasswordToken(username, password.toCharArray());// 2) TODO 开启“记住我”功能if(remember!nullremember.equals(on)){token.setRememberMe(true);}else {token.setRememberMe(false);}// 3) TODO 利用 subject 进行登录subject.login(token);// 4) TODO 记录登录日志 (暂时不做)return REDIRECT /;}
3.实现Realm类
需要实现Realmedia中的三个方法
doGetAuthenticationInfo()方法 在该方法中需要完成认证信息的准备。setCredentialsMatcher()方法需要在该方法中重新实现一个CredentialsMatcher()类中的接口用来提供验证密码的正确性然后将新的CredentialsMatcher对象当作参数调用父类的setCredentialsMatcher()方法。doGetAuthorizationInfo()方法该方法中需要进行授权信息准备准备好用户的权限信息将来使用AOP进行权限过滤时会用到。
下边是自己实现的ShiroDatabaseRealm类
doGetAuthenticationInfo()方法中返回了一个SimpleAuthenticationInfo对象SimpleAuthenticationInfo 表示认证信息 principal 表示用户信息一般就是一个用户对象里面会包含角色信息credentail 表示密码信息从数据库获取一般是加密后的realmName表示当前的Realm的名字 SimpleAuthenticationInfo第一个参数传递了一个ShiorUser对象而不是直接的User对象是因为在ShiorUser对象中多了一些存储权限路径等信息的属性。doGetAuthorizationInfo()方法返回一个SimpleAuthorizationInfo 对象使用setRoles设置其角色集合setStringPermissions设置权限集合url访问列表
Component
DependsOn({userService, deptService, roleService})
Slf4j
public class ShiroDatabaseRealm extends AuthorizingRealm {Autowiredprivate UserService userService;Autowiredprivate DeptService deptService;Autowiredprivate RoleService roleService;// TODO 1. 完成 认证信息 准备Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {// a. TODO 此 token 就是登录时封装的 token 对象UsernamePasswordToken token1(UsernamePasswordToken)token;// b. TODO 可以利用 userService 中的方法获取 用户对象User user userService.selectByAccount(token1.getUsername());// c. TODO 记得密码验证不需要在这里做你要做的判断有两件事// d. TODO 第一判断 数据库中该用户是否存在不存在则抛出 shiro 的 UnknownAccountException 异常if(usernull) {throw new UnknownAccountException();}// e. TODO 第二判断 该用户是否被冻结如被冻结则抛出 shiro 的 LockedAccountException 异常if(user.getStatus()FREEZE){throw new LockedAccountException();}// f. TODO 将数据库用户信息 User 对象转换 为 ShiroUser 对象ShiroUser shiroUsertoShiroUser(user);// g. TODO 将认证信息ShiroUser 对象、数据库密码、realm 名称通过 getName() 得到) 封装至 SimpleAuthenticationInfo 并返回SimpleAuthenticationInfo root new SimpleAuthenticationInfo(shiroUser, user.getPassword(), this.getName());/*注意* 验证密码的操作是 shiro 框架完成的不需要主动调用只需要提供密码验证的 CredentialsMatcher 对象* 验证成功进入 successUrl 验证失败进入 loginUrl* 成功后会将 SimpleAuthenticationInfo 中的 principal 信息存入 session 以后可以通过 Subject 对象获得* 成功后还会做一些 RemeberMe cookie 的生成并返回操作也无需我们干预*/return root;}/* ShiroUser 的作用1. 用来保存认证信息中的用户数据即 principal将来存入 session以便在登录期间使用2. 其中除了用户数据还包括了用户的角色信息其属性都是根据需要自定义的3. 为什么不直接用 User ? 是因为认证过程中的很多属性包括将来页面要显示的属性 User 对象中没有因此用 ShiroUser 来保存更多的信息*/private ShiroUser toShiroUser(User user) {ShiroUser shiroUser new ShiroUser();shiroUser.setId(user.getUserId());shiroUser.setAccount(user.getAccount());shiroUser.setDeptId(user.getDeptId());shiroUser.setDeptName(deptService.selectName(user.getDeptId()));shiroUser.setName(user.getName());shiroUser.setEmail(user.getEmail());shiroUser.setAvatar(user.getAvatar());String[] split user.getRoleId().split(,);ListLong roleIds new ArrayList();ListString roleNames new ArrayList();for (String s : split) {Long roleId Long.valueOf(s);roleIds.add(roleId);String roleName roleService.selectName(roleId);roleNames.add(roleName);}shiroUser.setRoleList(roleIds);shiroUser.setRoleNames(roleNames);log.debug( user {} roles: {}, user.getName(), shiroUser.getRoleNames());return shiroUser;}// TODO 2. 提供密码验证 CredentialsMatcherOverridepublic void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {CredentialsMatcher matcher new CredentialsMatcher() {Overridepublic boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {// TODO token 是表单提交过来的数据 info 是在 doGetAuthenticationInfo 步骤返回的认证信息UsernamePasswordToken token1(UsernamePasswordToken)token;char[] password token1.getPassword();String pass_user new String(password);String pass_databases info.getCredentials().toString();// TODO 使用 BCrypt 算法验证密码的正确性返回值即表示验证是否通过return BCrypt.checkpw(pass_user, pass_databases);// 验证不通过会由框架抛出 IncorrectCredentialsException 异常}};super.setCredentialsMatcher(matcher);}// TODO 3. 完成 授权信息 准备// 当需要进行权限验证时会调用 doGetAuthorizationInfo 获得当前用户已认证的权限信息只会执行一次放入缓存当中Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {ShiroUser shiroUser (ShiroUser) principals.getPrimaryPrincipal();// a. TODO 获取用户的所有 url 访问权限可以通过 roleService.selectPermissionURL 获取一个角色的 url 访问列表// 注意一个用户可能会有多个角色LinkedHashSetString set new LinkedHashSet();for (Long roleId : shiroUser.getRoleList()) {ListString strings roleService.selectPermissionURL(roleId);set.addAll(strings);}// b. TODO 准备一个 SimpleAuthorizationInfo 对象应设置其角色集合权限集合url访问列表并返回/* 注意这些集合中都是字符串表示的角色和权限后续可以通过 Subject 对象中的相关方法来使用这里准备的数据如* subject.hasRole(角色名) 判断用户是否有某个角色* subject.isPermitted(权限名) 判断用户是否有某个权限* 更多方法参考 shiro 的 Subject 接口说明*/LinkedHashSetString roleNames new LinkedHashSet();roleNames.addAll(shiroUser.getRoleNames());SimpleAuthorizationInfo info new SimpleAuthorizationInfo();info.setRoles(roleNames);info.setStringPermissions(set);return info;}
}
4. 在AOP中进行权限过滤
在需要进行权限过滤的controller方法上加Permission注解。 若该方法必须某个角色才能访问则Permission(“角色名”)
得到subject Subject subject SecurityUtils.getSubject();判断是否含有所有角色 subject.hasAllRoles()判断是否含有URL subject.isPermitted() /*** 权限检查的aop*/
// 0. TODO 打开 Aspect 注解
Aspect
Component
Order(200)
Slf4j
public class ShiroPermissionAop {// 1. TODO 控制器内需要权限控制的方法上都加了 Permission 自定义注解添加合适的切点Around(annotation(cn.stylefeng.guns.core.common.annotion.Permission))public Object doPermission(ProceedingJoinPoint pjp) throws Throwable {// a. 获取当前的请求路径已实现String requestURI getRequestURI();// b. 获取当前的控制器方法对象已实现Method method getMethod(pjp);// c. TODO 拿到方法的 Permission 注解做进一步判断Permission annotation method.getAnnotation(Permission.class);String[] roleNames annotation.value();// d. TODO 分支1如果 Permission 上有角色调用 checkRoles 进一步判断if(annotation.value().length0){boolean pass checkRoles(roleNames);;if(!pass){throw new NoPermissionException(没有权限);}}// e. TODO 分支2如果没有角色那么进行所有路径匹配检查调用 checkPermission 进一步判断else{boolean pass checkPermission(requestURI);if(!pass){throw new NoPermissionException(没有权限);}}// f. TODO 以上分支检查通过使用 pjp 放行 不通过抛出 NoPermissionException// 注意放行的话应该 将 pjp 执行目标方法的结果返回return pjp.proceed();}private Method getMethod(ProceedingJoinPoint point) {MethodSignature ms (MethodSignature) point.getSignature();return ms.getMethod();}private String getRequestURI() {HttpServletRequest request HttpContext.getRequest();String requestURI request.getRequestURI().replaceFirst(ConfigListener.getConf().get(contextPath), );log.debug( current uri: {}, requestURI);return requestURI;}public boolean checkRoles(Object[] permissions) {String[] roleNames (String[]) permissions;Subject subject SecurityUtils.getSubject();return subject.hasAllRoles(Arrays.asList(roleNames));}private boolean checkPermission(String requestURI) {Subject subject SecurityUtils.getSubject();return subject.isPermitted(requestURI);}
}