Spring Boot整合Shiro实现前后端分离
Apache Shiro是Java的一个安全框架。功能强大,使用简单的Java安全框架,它为开发人员提供一个直观而全面的认证,授权,加密及会话管理的解决方案。
创新互联公司是创新、创意、研发型一体的综合型网站建设公司,自成立以来公司不断探索创新,始终坚持为客户提供满意周到的服务,在本地打下了良好的口碑,在过去的10余年时间我们累计服务了上千家以及全国政企客户,如成都被动防护网等企业单位,完善的项目管理流程,严格把控项目进度与质量监控加上过硬的技术实力获得客户的一致赞扬。
Shiro基本功能点如下所示:
Shiro工作流程如下所示:
Shiro内部架构如下所示:
本文实现源码如下,欢迎Star和Fork。
实现思路:用户登录时生成token信息,设置过期时间,使用Redis存储。前端调用接口时将token作为参数传给服务端,服务端根据token信息认证用户。
自定义AuthFilter过滤器,继承AuthenticatingFilter重写createToken、isAccessAllowed、onAccessDenied、onLoginFailure方法。
AuthenticatingFilte类executeLogin方法如下所示:
用户登录时删除旧Token信息,重新生成Token信息,退出登录时删除Token信息。 使用Redis存储Token信息时,同时存储已用户ID为键,Token为值和已Token为键、用户ID为值的信息 。
整体实现流程图如下所示, 图源来自参考链接一,侵删 。
实现思路:自定义ModularRealmAuthenticator管理多Realm,结合自定义认证Token关联不同的Realm。
SecurityManager和ModularRealmAuthenticator配置如下:
自定义认证Token,重写getCredentials方法, 根据loginType返回不同的比较对象 。
普通登陆Realm认证逻辑如下所示:
Token关联的Realm认证逻辑如下所示:
自定义ModularRealmAuthenticator,管理多Realm,实现逻辑如下所示:
用户登录,返回生成的token信息:
用户携带token信息查询文章( 有对应权限 ):
用户携带token信息删除用户( 无权限 ):
用户携带token信息退出登录:
用户退出登录后携带原token信息删除用户:
如何正确的使用shiro
从来没接触过shiro Java安全框架,突然有一天需要要用用户登陆验证和用户角色权限的任务,而且是针对shiro 进行整合,开始收到任务,心都有点凉凉的。经过一轮的搜索,感觉没多大的收获。很多用户的角色都是写在xml配置文件中。觉得太不人性化了,想换个用户角色还得改xml?我觉得这么强大的框架应该不可能这么狗血的存在。然后认真的看文档,发现真的是可以直接读取数据库的。我把我搭建的流程发布在此。有问题的可以交流交流。我写的也并不是正确的,只能参考参考。
1.web.xml的配置
listener
listener-classorg.apache.shiro.web.env.EnvironmentLoaderListener/listener-class
/listener
filter
filter-nameshiroFilter/filter-name
filter-classorg.apache.shiro.web.servlet.ShiroFilter/filter-class
/filter
filter-mapping
filter-nameshiroFilter/filter-name
url-pattern/*/url-pattern
/filter-mapping
2.shiro.ini配置
[main]
[filters]
#自定义realm
shiroAuthorizingRealm = com.frame.security.ShiroAuthorizingRealm
securityManager.realm = $shiroAuthorizingRealm
# 声明一个自定义的用户校验拦截器
customFormAuthenticationFilter = com.frame.security.CustomFormAuthenticationFilter
# 声明一个自定义的用户角色权限拦截器
customPermissionsAuthorizationFilter = com.frame.security.CustomPermissionsAuthorizationFilter
#cache
shiroCacheManager = org.apache.shiro.cache.ehcache.EhCacheManager
shiroCacheManager.cacheManagerConfigFile = classpath:ehcache.xml
securityManager.cacheManager = $shiroCacheManager
#session
sessionDAO = org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO
sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager
sessionManager.sessionDAO = $sessionDAO
securityManager.sessionManager = $sessionManager
securityManager.sessionManager.globalSessionTimeout = 1800000
securityManager = org.apache.shiro.web.mgt.DefaultWebSecurityManager
[urls]
/admin/user/login = anon
/admin/user/logout = anon
/admin/user/registered = anon
/admin/** = customFormAuthenticationFilter,customPermissionsAuthorizationFilter
从shiro.ini配置中可以看出,需要三个文件,分别为ShiroAuthorizingRealm.java(realm文件),CustomFormAuthenticationFilter.java(自定义用户登陆验证文件),CustomPermissionsAuthorizationFilter(自定义用户角色权限文件);
在urls配置中可以看出不需要拦截的url后面加上anon便可,但有先后顺序。
缓存是使用ehcache
3.ehcache.xml配置
cache name="defaultCache" maxElementsInMemory="500"
maxElementsOnDisk="10000000" eternal="true" overflowToDisk="true"
diskSpoolBufferSizeMB="50" /
cache name="shiro-activeSessionCache" maxElementsInMemory="500"
maxElementsOnDisk="10000000" eternal="true" overflowToDisk="true"
diskSpoolBufferSizeMB="50" /
cache name="jdbcRealm.authorizationCache" maxElementsInMemory="500"
maxElementsOnDisk="10000000" eternal="true" overflowToDisk="true"
diskSpoolBufferSizeMB="50" /
cache name="authorization" maxElementsInMemory="500"
timeToLiveSeconds="3600" eternal="false" overflowToDisk="false" /
4.ShiroAuthorizingRealm.java
public class ShiroAuthorizingRealm extends AuthorizingRealm {
private AuthorityService authorityService = FrameContext.getBean(AuthorityService.class);
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("=======doGetAuthenticationInfo=======");
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
String username = userToken.getUsername();
String password = String.valueOf(userToken.getPassword());
User user = User.dao.findFirst("select * from m_user where account = ?", username);
if (user != null) {//下面可以做一些登陆的操作,密码错误,用户状态等等
if(MD5Encoder.validPassword(password, user.getPassword())==false){
throw new UnknownAccountException();
}
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, getName());
return info;
} else {
return null;
}
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("=======doGetAuthorizationInfo=======");
User user = (User) principals.getPrimaryPrincipal();
if(user!=null){//从数据库中读取用户的角色权限,
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
ListString perms = authorityService.getUrlByUser(user);
if(perms!=nullperms.size()0){//调用addStringPermissions方法把用户的权限信息添加到info中,可以addRoles方法把用户的角色添加到了info中
info.addStringPermissions(perms);
}
return info;
}
return null;
}
}
5.CustomFormAuthenticationFilter.java
public class CustomFormAuthenticationFilter extends FormAuthenticationFilter {
private final static Logger log = Logger.getLogger(CustomFormAuthenticationFilter.class);
private static final String contentType = "application/json; charset=UTF-8";
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpRequest = WebUtils.toHttp(request);
HttpServletResponse httpResponse = WebUtils.toHttp(response);
if (isLoginRequest(request, response)) {
if (isLoginSubmission(request, response)) {
if (log.isTraceEnabled()) {
log.trace("Login submission detected. Attempting to execute login.");
}
return executeLogin(request, response);
} else {
if (log.isTraceEnabled()) {
log.trace("Login page view.");
}
return true;
}
} else {
ResultObject result = new ResultObject(false, "401", "没有授权,请先登录", null);
renderJson(httpResponse, result);
return false;
}
}
private void renderJson(HttpServletResponse response, Object object) {
String jsonText = JsonKit.toJson(object);
PrintWriter writer = null;
try {
response.setHeader("Pragma", "no-cache"); // HTTP/1.0 caches might not implement Cache-Control and might only implement Pragma: no-cache
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
response.setContentType(contentType);
writer = response.getWriter();
writer.write(jsonText);
writer.flush();
} catch (IOException e) {
throw new RenderException(e);
}
finally {
if (writer != null) {
writer.close();
}
}
}
}
6.CustomPermissionsAuthorizationFilter.java
public class CustomPermissionsAuthorizationFilter extends PermissionsAuthorizationFilter {
private static final String contentType = "application/json; charset=UTF-8";
private AuthorityService authorityService = McmsContext.getBean(AuthorityService.class);
@Override
public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {
if(getMappedValue(request)!=null){
return super.isAccessAllowed(request, response, getMappedValue(request));
}
return false;
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
// TODO Auto-generated method stub
HttpServletRequest httpRequest = WebUtils.toHttp(request);
HttpServletResponse httpResponse = WebUtils.toHttp(response);
String path = httpRequest.getServletPath();
Subject subject = getSubject(request, response);
if (subject.isPermitted(path)) {
return true;
} else {
ResultObject result = new ResultObject(false, "401", "抱歉,您没有该权限!", null);
renderJson(httpResponse, result);
return false;
}
}
/**
* 得到mappedValue,相当于perms[user:add]中的“user:add”
* @param path
* @return
*/
public String[] getMappedValue(ServletRequest request) {
HttpServletRequest req = (HttpServletRequest) request;
String path = req.getServletPath();
String code = getCodesByPath(path);
if(null == code) {
return null;
}
return new String[]{code};
}
/**
* 根据访问路径获取权限代码
* @param path
* @return
*/
public String getCodesByPath(String path) {
User user = (User) SecurityUtils.getSubject().getPrincipal();
String pers = authorityService.getUrlByUserPath(path,user);
return Optional.ofNullable(pers).orElse(null);
}
private void renderJson(HttpServletResponse response, Object object) {
String jsonText = JsonKit.toJson(object);
PrintWriter writer = null;
try {
response.setHeader("Pragma", "no-cache"); // HTTP/1.0 caches might not implement Cache-Control and might only implement Pragma: no-cache
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
response.setContentType(contentType);
writer = response.getWriter();
writer.write(jsonText);
writer.flush();
} catch (IOException e) {
throw new RenderException(e);
}
finally {
if (writer != null) {
writer.close();
}
}
}
}
7.用户登陆入口
public void login() {
String account = getPara("account");
String password = getPara("password");
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken tokens = new UsernamePasswordToken(account, password);
tokens.setRememberMe(false);
try {
subject.login(tokens);
User user = (User) subject.getPrincipal();
loginSuccess(user);
UserVo userVo = convertToUserVO(user);
renderSucessResult(userVo);
} catch (UnknownAccountException ue) {
tokens.clear();
renderFailedResult("登录失败!无效的账号或密码!");
} catch (IncorrectCredentialsException ie) {
tokens.clear();
renderFailedResult("用户已注销!");
} catch(LockedAccountException le){
tokens.clear();
renderFailedResult("账号被锁定!");
} catch (RuntimeException re) {
re.printStackTrace();
tokens.clear();
renderFailedResult("登录失败!");
}
}
数据库可以自己去设计,这里就不提供了。
参照上面的去整合框架,便可以使用了,这样搭建适合多种框架的整合。
如何使用Shiro实现不同用户登录成功后跳转到不同主页
登陆成功后获取 Subject 对象.
然后通过 Subject 对象来判断当前用户的角色/权限, 之后执行不同的跳转(直接在LoginAction中做).
我的登陆部分代码:
Java代码
UsernamePasswordToken token = new UsernamePasswordToken(name, password);
try {
SecurityUtils.getSubject().login(token);
Subject subject = SecurityUtils.getSubject();
// 这里可以调用subject 做判断
System.out.println("--------------------------------------------------------------");
Boolean isadmin = subject.hasRole("admin");
log.info("是否为管理员:"+isadmin);
System.out.println("--------------------------------------------------------------");
String userId = (String)subject.getPrincipal();
User user = userService.getById(userId);
ShiroUser shiroUser = shiroUserService.getByDyId(userId);
if(shiroUser == null){
this.addActionError(getText("login.failure"));
return ERROR;
}else{
int used = shiroUser.getUsed();
if(used == 1){
this.addActionError(getText("login.noused"));
return ERROR;
}
}
Session session = subject.getSession(true);
session.setAttribute(LoginAction.USER_KEY, user);
session.setAttribute(LoginAction.SHIRO_USER_KEY, shiroUser);
log.info("set workflow define to session");
session.setAttribute("ptDefine", WorkflowContext.getPtDefine());
} catch (AuthenticationException e) {
log.info(e.getMessage());
this.addActionError(getText("login.failure"));
}
if (this.hasErrors()) {
log.info("login erro ...");
return ERROR;
}
session如何在java代码中判断是否过期
系统框架使用的springmvc 。。。。
在controller层上做了拦截器,添加了自定义标签,使用了该标签则需要校验session是否过期,过期则跳转至登录页面,但是系统用到了shiro,请问在java代码中如何判断seesion已经过期
Subject currentUser = SecurityUtils.getSubject();
Session session = currentUser.getSession();
//过期,则跳转登录页面重新登录
if () { //就是这里不知道如何写!!!!!!!!!!!!!!!!!!!!!
dosomething;。。。。
}
shiro配置如下:
!-- 会话DAO --
bean id="sessionDAO" class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO"
property name="activeSessionsCacheName" value="shiro-activeSessionCache"/
property name="sessionIdGenerator" ref="sessionIdGenerator"/
/bean
!-- 会话验证调度器 --
bean id="sessionValidationScheduler" class="org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler"
property name="sessionValidationInterval" value="1800000"/
property name="sessionManager" ref="sessionManager"/
/bean
!-- 会话管理器 --
bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"
property name="globalSessionTimeout" value="1800000"/!-- 回话有效时间30分钟 --
property name="deleteInvalidSessions" value="true"/
property name="sessionValidationSchedulerEnabled" value="true"/
property name="sessionValidationScheduler" ref="sessionValidationScheduler"/
property name="sessionDAO" ref="sessionDAO"/
property name="sessionIdCookieEnabled" value="true"/
property name="sessionIdCookie" ref="sessionIdCookie"/
/bean
希望能帮到楼主, 谢谢
当前文章:shriojava代码 java shrio
转载来源:http://scgulin.cn/article/hjdddc.html