目前在企业级项目里做权限安全方面喜欢使用Apache开源的Shiro框架或者Spring框架的子框架Spring Security。
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码学和会话管理。
Shiro框架具有轻便,开源的优点,所以本博客介绍基于Shiro的登录验证实现。
在maven里加入shiro需要的jar
org.apache.shiro
shiro-all
1.2.3
在web.xml加上Shiro过滤器配置:
shiroFilter
org.springframework.web.filter.DelegatingFilterProxy
targetFilterLifecycle
true
shiroFilter
/*
编写shiro的ShiroRealm类:
package org.muses.jeeplatform.core.security.shiro;
import javax.annotation.Resource;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.muses.jeeplatform.model.entity.User;
import org.muses.jeeplatform.service.UserService;
/**
* @description 基于Shiro框架的权限安全认证和授权
* @author Nicky
* @date 2017年3月12日
*/
public class ShiroRealm extends AuthorizingRealm {
/**注解引入业务类**/
@Resource
UserService userService;
/**
* 登录信息和用户验证信息验证(non-Javadoc)
* @see org.apache.shiro.realm.AuthenticatingRealm#doGetAuthenticationInfo(AuthenticationToken)
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String)token.getPrincipal(); //得到用户名
String password = new String((char[])token.getCredentials()); //得到密码
User user = userService.findByUsername(username);
/**检测是否有此用户 **/
if(user == null){
throw new UnknownAccountException();//没有找到账号异常
}
/**检验账号是否被锁定 **/
if(Boolean.TRUE.equals(user.getLocked())){
throw new LockedAccountException();//抛出账号锁定异常
}
/**AuthenticatingRealm使用CredentialsMatcher进行密码匹配**/
if(null != username && null != password){
return new SimpleAuthenticationInfo(username, password, getName());
}else{
return null;
}
}
/**
* 授权查询回调函数, 进行鉴权但缓存中无用户的授权信息时调用,负责在应用程序中决定用户的访问控制的方法(non-Javadoc)
* @see AuthorizingRealm#doGetAuthorizationInfo(PrincipalCollection)
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) {
String username = (String)pc.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.setRoles(userService.getRoles(username));
authorizationInfo.setStringPermissions(userService.getPermissions(username));
System.out.println("Shiro授权");
return authorizationInfo;
}
@Override
public void clearCachedAuthorizationInfo(PrincipalCollection principals) {
super.clearCachedAuthorizationInfo(principals);
}
@Override
public void clearCachedAuthenticationInfo(PrincipalCollection principals) {
super.clearCachedAuthenticationInfo(principals);
}
@Override
public void clearCache(PrincipalCollection principals) {
super.clearCache(principals);
}
}
在Spring框架里集成Shiro,加入配置
/static/** = anon
/upload/** = anon
/plugins/** = anon
/code = anon
/login = anon
/logincheck = anon
/** = authc
登录验证控制类实现:
package org.muses.jeeplatform.web.controller;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.muses.jeeplatform.core.Constants;
import org.muses.jeeplatform.model.entity.Menu;
import org.muses.jeeplatform.model.entity.Permission;
import org.muses.jeeplatform.model.entity.Role;
import org.muses.jeeplatform.model.entity.User;
import org.muses.jeeplatform.service.MenuService;
import org.muses.jeeplatform.service.UserService;
import org.muses.jeeplatform.utils.Tools;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
/**
* @description 登录操作的控制类,使用Shiro框架,做好了登录的权限安全认证,
* getRemortIP()方法获取用户登录时的ip并保存到数据库
* @author Nicky
* @date 2017年3月15日
*/
@Controller
public class LoginController extends BaseController {
@Autowired
UserService userService;
@Autowired
MenuService menuService;
/**
* 获取登录用户的IP
* @throws Exception
*/
public void getRemortIP(String username) {
HttpServletRequest request = this.getRequest();
Map map = new HashMap();
String ip = "";
if (request.getHeader("x-forwarded-for") == null) {
ip = request.getRemoteAddr();
}else{
ip = request.getHeader("x-forwarded-for");
}
map.put("username", username);
map.put("loginIp", ip);
userService.saveIP(map);
}
/**
* 访问后台登录页面
* @return
* @throws Exception
*/
@RequestMapping(value="/login",produces="text/html;charset=UTF-8")
public ModelAndView toLogin()throws ClassNotFoundException{
ModelAndView mv = this.getModelAndView();
mv.setViewName("admin/frame/login");
return mv;
}
/**
* 基于Shiro框架的登录验证,页面发送JSON请求数据,
* 服务端进行登录验证之后,返回Json响应数据,"success"表示验证成功
* @param request
* @return
* @throws Exception
*/
@RequestMapping(value="/logincheck", produces="application/json;charset=UTF-8")
@ResponseBody
public String loginCheck(HttpServletRequest request)throws AuthenticationException{
JSONObject obj = new JSONObject();
String errInfo = "";//错误信息
String logindata[] = request.getParameter("LOGINDATA").split(",");
if(logindata != null && logindata.length == 3){
//获取Shiro管理的Session
Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession();
String codeSession = (String)session.getAttribute(Constants.SESSION_SECURITY_CODE);
String code = logindata[2];
/**检测页面验证码是否为空,调用工具类检测**/
if(Tools.isEmpty(code)){
errInfo = "nullcode";
}else{
String username = logindata[0];
String password = logindata[1];
if(Tools.isNotEmpty(codeSession) && codeSession.equalsIgnoreCase(code)){
//Shiro框架SHA加密
String passwordsha = new SimpleHash("SHA-1",username,password).toString();
System.out.println(passwordsha);
//检测用户名和密码是否正确
User user = userService.doLoginCheck(username,passwordsha);
if(user != null){
if(Boolean.TRUE.equals(user.getLocked())){
errInfo = "locked";
}else{
//Shiro添加会话
session.setAttribute("username", username);
session.setAttribute(Constants.SESSION_USER, user);
//删除验证码Session
session.removeAttribute(Constants.SESSION_SECURITY_CODE);
//保存登录IP
getRemortIP(username);
/**Shiro加入身份验证**/
Subject sub = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
sub.login(token);
}
}else{
//账号或者密码错误
errInfo = "uerror";
}
if(Tools.isEmpty(errInfo)){
errInfo = "success";
}
}else{
//缺少参数
errInfo="codeerror";
}
}
}
obj.put("result", errInfo);
return obj.toString();
}
/**
* 后台管理系统主页
* @return
* @throws Exception
*/
@RequestMapping(value="/admin/index")
public ModelAndView toMain() throws AuthenticationException{
ModelAndView mv = this.getModelAndView();
/**获取Shiro管理的Session**/
Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession();
User user = (User)session.getAttribute(Constants.SESSION_USER);
if(user != null){
...//业务实现
}else{
//会话失效,返回登录界面
mv.setViewName("admin/frame/login");
}
mv.setViewName("admin/frame/index");
return mv;
}
/**
* 注销登录
* @return
*/
@RequestMapping(value="/logout")
public ModelAndView logout(){
ModelAndView mv = this.getModelAndView();
/**Shiro管理Session**/
Subject sub = SecurityUtils.getSubject();
Session session = sub.getSession();
session.removeAttribute(Constants.SESSION_USER);
session.removeAttribute(Constants.SESSION_SECURITY_CODE);
/**Shiro销毁登录**/
Subject subject = SecurityUtils.getSubject();
subject.logout();
/**返回后台系统登录界面**/
mv.setViewName("admin/frame/login");
return mv;
}
}
前端Ajax和JQeury校验实现:
/**客户端校验**/
function checkValidity() {
if ($("#username").val() == "") {
$("#username").tips({
side : 2,
msg : '用户名不得为空',
bg : '#AE81FF',
time : 3
});
$("#username").focus();
return false;
}
if ($("#password").val() == "") {
$("#password").tips({
side : 2,
msg : '密码不得为空',
bg : '#AE81FF',
time : 3
});
$("#password").focus();
return false;
}
if ($("#code").val() == "") {
$("#code").tips({
side : 1,
msg : '验证码不得为空',
bg : '#AE81FF',
time : 3
});
$("#code").focus();
return false;
}
return true;
}
/**服务器校验**/
function loginCheck(){
if(checkValidity()){
var username = $("#username").val();
var password = $("#password").val();
var code = username+","+password+","+$("#code").val();
$.ajax({
type: "POST",//请求方式为POST
url: 'logincheck',//检验url
data: {LOGINDATA:code,tm:new Date().getTime()},//请求数据
dataType:'json',//数据类型为JSON类型
cache: false,//关闭缓存
success: function(data){//响应成功
if("success" == data.result){
$("#login").tips({
side : 1,
msg : '正在登录 , 请稍后 ...',
bg : '#68B500',
time : 10
});
window.location.href="admin/index";
}else if("uerror" == data.result){
$("#username").tips({
side : 1,
msg : "用户名或密码有误",
bg : '#FF5080',
time : 15
});
$("#username").focus();
}else if("codeerror" == data.result){
$("#code").tips({
side : 1,
msg : "验证码输入有误",
bg : '#FF5080',
time : 15
});
$("#code").focus();
}else if("locked" == data.result){
alert('您的账号被锁定了,呜呜');
}else{
$("#username").tips({
side : 1,
msg : "缺少参数",
bg : '#FF5080',
time : 15
});
$("#username").focus();
}
}
});
}
}
登录成功,Session会话过期,需要重新登录,保证系统安全性
本博客只提供基于Shiro的登录验证实现,具体代码可以去我的github下载:https://github.com/u014427391/jeeplatform
欢迎star