目前在企业级项目里做权限安全方面喜欢使用 Apache 开源的 Shiro 框架或者 Spring 框架的子框架 Spring Security。
Apache Shiro 是一个强大且易用的 Java 安全框架,执行身份验证、授权、密码学和会话管理。
Shiro 框架具有轻便,开源的优点,所以本博客介绍基于 Shiro 的登录验证实现。
本博客只提供基于 Shiro 的登录验证实现,具体代码可以去我的 github 下载: https://github.com/u014427391/jeeplatform 欢迎 star
在 maven 里加入 shiro 需要的 jar
<!--shiro start-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.2.3</version>
</dependency>
<!-- shiro end-->
在 web.xml 加上 Shiro 过滤器配置:
<!-- Shiro 过滤器配置 start -->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>
org.springframework.web.filter.DelegatingFilterProxy
</filter-class>
<init-param>
<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>
<!-- Shiro 过滤器配置 end -->
编写 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,加入配置
<!-- Shiro start -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="ShiroRealm" />
</bean>
<!-- 项目自定义的 Realm -->
<bean id="ShiroRealm" class="org.muses.jeeplatform.core.security.shiro.ShiroRealm" ></bean>
<!-- Shiro Filter -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<property name="loginUrl" value="/login" />
<property name="successUrl" value="/admin/index" />
<property name="unauthorizedUrl" value="/login" />
<property name="filterChainDefinitions">
<value>
/static/** = anon
/upload/** = anon
/plugins/** = anon
/code = anon
/login = anon
/logincheck = anon
/** = authc
</value>
</property>
</bean>
<!-- Shiro end -->
登录验证控制类实现:
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<String,String> map = new HashMap<String,String>();
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
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.