通常我们做一个后台系统,不可避免的需要做一些安全性的拦截,但是又不想做的太过麻烦,只要能够通过帐号密码登录基本上就差不多了,还可以拓展一些特定密钥免登录小功能。
今天给大家示范一个基于SpringBoot的实现的基本步骤:
功能:
- 登录
- 登出
- token免登录
构建基础配置项
1. 注册拦截器
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = {"扫描你的controller地址"})
public class MvcConfigBean extends WebMvcConfigurerAdapter {
@Autowired
private LoginHandler loginHandler;
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
super.addResourceHandlers(registry);
registry.addResourceHandler("/js/**").addResourceLocations("classpath:/static/js/");
registry.addResourceHandler("/css/**").addResourceLocations("classpath:/static/css/");
registry.addResourceHandler("/lib/**").addResourceLocations("classpath:/static/lib/");
registry.addResourceHandler("/fonts/**").addResourceLocations("classpath:/static/fonts/");
registry.addResourceHandler("/images/**").addResourceLocations("classpath:/static/images/");
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginHandler).addPathPatterns("/**")
// 排除登录的Service验证以及跳转登录页的信息
.excludePathPatterns(ServerConstants.LOGIN_HTML_PATH, ServerConstants.LOGIN_PATH)
// 过滤静态资源文件
.excludePathPatterns("/js/**", "/css/**", "/lib/**", "/fonts/**", "/images/**");
}
}
拦截器的实现
这里负责拦截所有的请求,统一要经过这边验证,如果没有携带token相关的,统一返回登录页。
@Component
public class LoginHandler implements HandlerInterceptor {
private Logger logger = LoggerFactory.getLogger(LoginHandler.class);
@Autowired
private JayServerProperties serverProperties;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
logger.info(request.getRequestURI().toString());
// 特定的token
String token = request.getParameter("token");
String defaultToken = serverProperties.getToken();
// 如果携带的是特定的token,那么直接放行
if (defaultToken.equals(token)) {
CookiesUtil.setCookie(response, ServerConstants.USER_SESSION_KEY, token, -1);
return true;
}
// 从客户端的cookie中获取登录token
Cookie cookieByName = CookiesUtil.getCookieByName(request, ServerConstants.USER_SESSION_KEY);
if (cookieByName != null && defaultToken.equals(cookieByName.getValue())) {
return true;
}
// 如果是session级别的token也直接放行
String user = (String)request.getSession().getAttribute(ServerConstants.USER_SESSION_KEY);
// 如果没有得到配置内的token,那么请登录
if (StringUtils.isEmpty(user)) { response.sendRedirect(ServerConstants.LOGIN_HTML_PATH);
logger.info("token 校验失败.");
return false;
}
return true;
}
}
配置文件
@ConfigurationProperties(prefix = "jay.server")
public class JayServerProperties {
private String token;
private String username;
private String password;
// 省略get set ...
}
可以在yml中配置帐号密码和特定的token信息
jay:
server:
token: xxxx
username: admin
password: xxxx
路由层
其中包含了登录验证、登录页、登出等请求处理。。
/**
* @author : liukx
* @describe :首页
* @time : 2019/3/1 - 16:31
*/
@Controller
@RequestMapping(value = "/")
public class IndexController {
final static private String page = "/";
@Autowired
private JayServerProperties serverProperties;
@RequestMapping(value = "/index.html", method = RequestMethod.GET)
public String index() throws Exception {
return page + "index";
}
@RequestMapping(value = "/login.html", method = RequestMethod.GET)
public String login() throws Exception {
return page + "login";
}
@RequestMapping(value = "/loginOut.html", method = RequestMethod.GET)
public String loginOut(HttpServletRequest request, HttpServletResponse response) throws Exception {
CookiesUtil.deleteCookieByName(request, response, ServerConstants.USER_SESSION_KEY);
return "redirect:/login.html";
}
@RequestMapping(value = "/login", method = RequestMethod.POST, produces = "application/json;charset=UTF-8")
@ResponseBody
public ResponseCommonModel loginService(HttpServletResponse response, @RequestBody LoginRequest loginRequest)
throws Exception {
String username = serverProperties.getUsername();
String password = serverProperties.getPassword();
String token = serverProperties.getToken();
String un = loginRequest.getUsername();
String pwd = loginRequest.getPassword();
if (username.equals(un) && password.equals(pwd)) {
CookiesUtil.setCookie(response, ServerConstants.USER_SESSION_KEY, token, -1);
return ResponseUtils.trues();
} else {
return ResponseUtils.falses(CommonEnums.LOGIN_ERROR);
}
}
其他
这里包含一些工具类、或者常量类.
public class CookiesUtil {
/**
* 根据名字获取cookie
*
* @param request
* @param name cookie名字
* @return Cookie
*/
public static Cookie getCookieByName(HttpServletRequest request, String name) {
Map<String, Cookie> cookieMap = readCookieMap(request);
if (cookieMap.containsKey(name)) {
Cookie cookie = (Cookie)cookieMap.get(name);
return cookie;
} else {
return null;
}
}
/**
* 将cookie封装到Map里面
*
* @param request
* @return Map<String, Cookie>
*/
private static Map<String, Cookie> readCookieMap(HttpServletRequest request) {
Map<String, Cookie> cookieMap = new HashMap<String, Cookie>();
Cookie[] cookies = request.getCookies();
if (null != cookies) {
for (Cookie cookie : cookies) {
cookieMap.put(cookie.getName(), cookie);
}
}
return cookieMap;
}
/**
* 保存Cookies
*
* @param response response响应
* @param name cookie的名字
* @param value cookie的值
* @param time cookie的存在时间(秒)
*/
public static HttpServletResponse setCookie(HttpServletResponse response, String name, String value, int time) {
// new一个Cookie对象,键值对为参数
Cookie cookie = new Cookie(name, value);
// tomcat下多应用共享
cookie.setPath("/");
// 如果cookie的值中含有中文时,需要对cookie进行编码,不然会产生乱码
try {
URLEncoder.encode(value, "utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
// 单位:秒
cookie.setMaxAge(time);
// 将Cookie添加到Response中,使之生效
response.addCookie(cookie); // addCookie后,如果已经存在相同名字的cookie,则最新的覆盖旧的cookie
return response;
}
/**
* <p>删除无效cookie</p>
* <p>无效?1.过时 2.未发布</p>
*
* @param request 请求
* @param response 响应
* @param deleteKey 需要删除cookie的名称
*/
public static void deleteCookieByName(HttpServletRequest request, HttpServletResponse response, String deleteKey)
throws NullPointerException {
Map<String, Cookie> cookieMap = readCookieMap(request);
for (String key : cookieMap.keySet()) {
if (key.equals(deleteKey)) {
Cookie cookie = cookieMap.get(key);
// 默认值是-1,即:关闭浏览器时就清除cookie;
// 当设置为0的时候:创建完cookie,使用后马上就删除;
// 因为时间到了,又因为,cookie没有清除方法,所以设置为 0,就相当于清除方法;
// 当设置时间大于0,当时间到达后就会自动删除
cookie.setMaxAge(0);//设置cookie有效时间为0
cookie.setPath("/");//不设置存储路径
response.addCookie(cookie);
}
}
}
}
常量类
public class ServerConstants {
public static String INDEX_HOME = "/index.html";
public static String USER_SESSION_KEY = "JAY_TOKEN";
public static String LOGIN_HTML_PATH = "/login.html";
public static String LOGIN_PATH = "/login";
}
免登陆的话: http://localhost:1111/index?token=xxxx就可以跳过登录直接访问了.目的是为了在手机端想直接访问监控数据,但是不用登录,在内部直接使用.但是千万不要泄露了.
当然免你还可以更强化一点,将token做个有效期,用个HashSet保存,过期了直接重新换个随机数或者其他的。生成之后通过钉钉或者邮箱直接携带token发送出去。有效期1个小时,每天固定生成24个大小的token。
以上就是登录拦截的简单的实现,主要是为了给有这些需求的人节省时间。
主要是涵盖了登录\登出\免登录这些功能,非常简单,可以基于以上代码进行改造。
有问题直接留言,我看到了会回复的。