话不多直接上代码:
@RequestMapping("/login")
public String login(Blogger blogger,HttpServletRequest request){
//System.out.println("BloggerController.login()......");
Subject subject = SecurityUtils.getSubject();//获得当前登录用户
UsernamePasswordToken token = new UsernamePasswordToken(blogger.getUserName(), CryptographyUtil.md5(blogger.getPassword(), "gcc"));
try {
subject.login(token);//身份认证,这时会调用自定义realm
return "redirect:/admin/main.jsp";
} catch (Exception e) {
e.printStackTrace();
request.setAttribute("blogger", blogger);//回显用户名和密码
request.setAttribute("errorInfo", "用户名或者密码错误");
return "login";
}
}
在shiro的官方API文档中,案例是以.ini文件来举例的,通过shiro的核心securityManager实例在读取配置文件的内容。如果我们希望通过数据库,并且自定义是什么表请往下看。
这里第四行代码,通过SecurityUtils类得到当前交互对象,一般就是登录的用户
第五行代码,将从页面获取的用户名和密码封装到一个token令牌中
try内的一行代码,对当前交互对象进行登录操作,执行login方法,下面就是关键,这时会执行如下代码:
/**
* 自定义realm
* @author gcc
*/
public class MyRealm extends AuthorizingRealm {
@Resource
private BloggerService bloggerService;
//为当前登录用户授予角色权限
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
//验证当前登录用户
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String userName=(String) token.getPrincipal();
Blogger blogger = bloggerService.getByUserName(userName);
if (blogger!=null) {
SecurityUtils.getSubject().getSession().setAttribute("currentUser", blogger);//把当前用户信息放入session
//AuthenticationInfo的第二个参数是数据库密码,内部实现会比对token的密码,如果错误,会返回报错信息, 在login()方法catch,页面提示错误
AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(blogger.getUserName(), blogger.getPassword(), "xx");//realname可以是随意的字符串
这里我们自定义了一个realm,并且继承抽象类AuthorizingRealm,因为是抽象类,就必须实现内部存在的抽象方法,这里有两个,其中优先执行第二个。我们来说明第二个类做了什么,首先他拿到了刚刚封装的token令牌,里面有用户名和密码,根据规范这里用来获取用户名。再之后就是自定义方法,通过用户名去自己定义的数据库和表中查询得到用户实体。到这一步心情突然好了吧,看你成功了一半了!
如果查询结果存在,则将用户信息放入session,这里可能思维敏捷的人发现了什么,我只是得到用户,但没有校验密码,怎么就能把信息放入session呢?这里记(问题一)
我们先往下看,将查询得到的用户名和密码交给授权信息这个构造方法,然后java封装的伟大就出现了,我们不用关心这个构造方法具体怎么做,我只要知道他会干什么。
这个构造方法会核对数据库得到的数据和token内的数据是否匹配,匹配就悄悄离开,否则例如密码错误,就会报错。而这个错误会通过throws往上抛出,被我们的catch抓到。这时可以在catch里做出补救措施,比如返回原先的用户名密码,提示错误,返回到登陆界面等等,也就解释了(问题一),既然会catch,放session也没事。
然后恍然大悟,哦,这个方法走完了。
之后是此类的第一个方法,用于获取角色和权限,这里暂时略过。再之后就是第一处代码中的登录成功,跳转页面,程序结束。
Stop!到这里是不是应该问,shiro怎么知道要走Myrealm!
这里环境是spring下,配置如下:
在web.xml中做以下配置:
<!-- shiro过滤器定义 -->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<!-- 该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理 -->
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
在applicationContext配置:<!-- 自定义Realm -->
<bean id="myRealm" class="com.gcc.realm.MyRealm"/>
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="myRealm"/>
</bean>
<!-- Shiro过滤器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- Shiro的核心安全接口,这个属性是必须的 -->
<property name="securityManager" ref="securityManager"/>
<!-- 身份认证失败,则跳转到登录页面的配置 -->
<property name="loginUrl" value="/login.jsp"/>
<!-- Shiro连接约束配置,即过滤链的定义 -->
<property name="filterChainDefinitions">
<value>
/login=anon
/admin/**=authc
</value>
</property>
</bean>
<!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!-- 开启Shiro注解 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
配置我全给了哦,这里我们只关心一部分。
web的配置是对所有路径都进行shiro的过滤,就是说不管你是啥路径,都要被shiro检查校验。
app配置文件上半部分很好理解,也是我们关心的,这里我们定义了bean,给出了myrealm路径,shiro的默认web securityManager读到这个路径,然后就执行了我们的自定义realm。
再往下则是刚刚略过的角色和权限认证的配置,见代码中说明,这里单独介绍下权限的过滤,如/login路径下方法不用通过角色也权限认证就能执行,anon就是这个意思。再如/admin/*路径下需要进行角色认证,authc就是这个意思。其它关键字大伙自个查api。
一旦走权限认证就会执行之前我略过没有说明的方法,这里进行说明(代码不是一体的,我另一个demo中的)
/**
* 为当前登录的用户授予角色和权限
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String userName=(String)principals.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo=new SimpleAuthorizationInfo();
Connection con=null;
try{
con=dbUtil.getCon();
authorizationInfo.setRoles(userDao.getRoles(con,userName));
authorizationInfo.setStringPermissions(userDao.getPermissions(con,userName));
}catch(Exception e){
e.printStackTrace();
}finally{
try {
dbUtil.closeCon(con);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return authorizationInfo;
}
简单易懂吧,我们通过dao方法查询得到权限和角色,然后交给授权信息实体,这里提醒一下,角色和权限的数据类型是set,取值最好有hashSet。然后我们就可以这样用:
@RequiresPermissions("sys:sysPosition:view")
@RequestMapping(value = {"list", ""})
public String list(SysPosition sysPosition, HttpServletRequest request, HttpServletResponse response, Model model) {
Page<SysPosition> page = sysPositionService.findPage(new Page<SysPosition>(request, response), sysPosition);
model.addAttribute("page", page);
return "modules/sys/sysPositionList";
}
这里关键看第一行,通过注解得到当前登录对象的权限,如果有则执行方法,没有就返回提示信息。也可以jsp中这样用:
<div class="form-actions">
<shiro:hasPermission name="sys:menu:edit"><input id="btnSubmit" class="btn btn-primary" type="submit" value="保 存"/> </shiro:hasPermission>
<input id="btnCancel" class="btn" type="button" value="返 回" onclick="history.go(-1)"/>
</div>
通过标签来让内容是否显示。
大功告成,简单的说明就到这里,内容有误的欢迎指出,谢谢了,各位同道中人!