最近项目需要利用shiro来进行权限管理实现,本人也是第一次接触,写的不好还请见谅。
首先shiro是一个java的安全框架,能够帮我们实现登录认证,权限认证等其他功能,如rememberMe。
主要关注点如下:
- Subject:可以看成当前用户
- Token:令牌,包含当前用户利用form表单提交的用户名和密码信息
- Authentication:身份认证,可以理解成当前用户的用户名和密码验证
- Authorization:权限认证,可以理解成当前用户访问某个资源时的权限判断
- Realm:安全数据源,可以理解成当前用户相关的身份信息及权限相关信息(暂时可以这么理解)
整体结构大致如下:
How
有关shiro的dependencies大致如下:(特别注明:这里页面模板引擎用的是Thymeleaf而不是jsp)
<!-- shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.2</version>
</dependency>
<!-- shiro -web -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.2.2</version>
</dependency>
<!-- shiro-spring -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.2.2</version>
</dependency>
<!-- 以下为thymeleaf权限标签,如果不是用Thymeleaf请忽略 -->
<!-- shiro-thymeleaf-tag -->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
</dependencies>
增加完shiro相关依赖就需要我们在spring的配置文件中增加如下配置:
<!-- 自定义的Realm -->
<bean id="userRealm" class="com.myjava.realm.UserRealm">
<property name="credentialsMatcher" ref="credentialsMatcher"></property>
</bean>
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="userRealm"/>
<!-- 会话管理及rememberMe,可以等到需要时再配置 -->
<property name="sessionManager" ref="defaultWebSessionManager"/>
<property name="rememberMeManager" ref="remembermeManager"/>
</bean>
<!-- 密码匹配器 -->
<bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<!-- 指定加密算法,此处是MD5 -->
<property name="hashAlgorithmName" value="md5"/>
<property name="hashIterations" value="2"></property>
</bean>
<!-- 相当于调用SecurityUtils.setSecurityManager(securityManager) -->
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="staticMethod"
value="org.apache.shiro.SecurityUtils.setSecurityManager"/>
<property name="arguments" ref="securityManager"/>
</bean>
<!-- Shiro生命周期处理器-->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
先从登录开始,其实shiro中的默认的过滤器就已经帮助我们实现从登录(login)到身份认证(authentication),我们只需要在shiro的配置文件中配置好过滤器即可,但是这里为了更清晰了解这个过程,先自己写一个登录的Controller,如下:
@RequestMapping("/login")
public String login(User user, ModelMap mm , HttpSession session,HttpServletResponse response,RedirectAttributes attrs ) {
UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername() , user.getPassword()) ;
//rememberMe
token.setRememberMe(true);
//获取主体
Subject subject = SecurityUtils.getSubject() ;
try {
//登录
subject.login(token);
} catch (AuthenticationException e) {
System.out.println("---------------用户名或者密码错误-------------------");
attrs.addFlashAttribute("msg","用户名或密码错误") ;
e.printStackTrace();
return "redirect:toLogin.action" ;
}
subject.getSession().setAttribute("user", userService.getByUserName(user.getUsername()));
return "redirect:index.action";
}
由于用户输入的username及password需要利用数据库中的信息进行对比,这必然涉及到数据库的交互,而shiro将这个交互封装到Realm中,即shiro通过Realm获取信息,而realm才真正从数据库中获取信息。所以我们需要自定义一个Realm,如下:
public class UserRealm extends AuthorizingRealm{
@Autowired
private RoleService roleService ;
@Autowired
private UserService userService ;
@Autowired
private PermissionService permissionService ;
//保存当前用户的授权信息
private SimpleAuthorizationInfo info = new SimpleAuthorizationInfo() ;
/**
* 获取用户授权信息
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = (String) principals.getPrimaryPrincipal();
/**
* 将用户角色信息放入SimplAuthorizationInfo
*/
info.setRoles(this.addRoles(username));
/**
* 将用户权限信息放入SimpleAuthorizationInfo中
*/
info.setStringPermissions(this.addPermissions(username));
return info;
}
/**
* 获取用户身份信息
* CredentialsMatcher会进行密码验证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException {
//用户输入的用户名和密码
String username = (String) token.getPrincipal() ;
//数据库查找的user
User accountUser = userService.getByUserName(username) ;
//验证
if(accountUser == null) throw new UnknownAccountException() ;
//会调用credentialMatcher进行密码验证
return new SimpleAuthenticationInfo(accountUser.getUsername(),accountUser.getPassword(),new SimpleByteSource(accountUser.getSalt()),getName());
}
注意以上override的两个方法,doGetAuthenticationInfo是获取用户认证信息,doGetAuthorizationInfo是获取用户权限信息。
执行流程如下:
1. 将form表单传来的用户信息封装到UsernamePasswordToken
中。
2. 通过Subject subject = SecurityUtils.getSubject() ;
获取subject主体。
3. 执行登录行为subject.login(token);
之后委托给securityManager执行真正的login操作,也是说securityManager才是真正的核心。
4. shiro会自动获取配置好的Realm(可以有多个),如上面自定义的UserRealm,在Realm中获取当前username对应的用户相关信息(如username,password……)
5. 内部会调用之前配置好的HashedCredentialsMatcher密码匹配器进行密码验证(期间会根据指定的算法进行密码的加密),如果匹配失败则会抛出IncorrectCredentialsException。
登录成功后subject.isAuthenticated()就会return true.至此登录的雏形就完成了。
未完待续……