这里详细记录一次springboot使用shiro进行登录验证操作,同时结合MD5加密技术,实现web后台的登录功能。这里并不进行shiro认证框架的分析介绍,知识介绍一下如何使用。
:一:首先是pom.xml清单
org.springframework.boot
spring-boot-starter-jdbc
org.springframework.boot
spring-boot-starter-thymeleaf
org.springframework.boot
spring-boot-starter-web
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.3.2
mysql
mysql-connector-java
runtime
org.springframework.boot
spring-boot-starter-test
test
org.apache.shiro
shiro-core
${shiro.version}
org.apache.shiro
shiro-spring
${shiro.version}
commons-codec
commons-codec
org.apache.commons
commons-lang3
3.6
二:shiro配置
@Configuration
public class ShiroConfig {
private static String loginUrl = "/login";
@Bean
public UserRealm userRealm(){
UserRealm userRealm = new UserRealm();
return userRealm;
}
/*
配置安全管理器
*/
@Bean
public SecurityManager manager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm());
return securityManager;
}
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager manager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(manager);
//身份认证失败,则跳转到登录页面的配置
bean.setLoginUrl(loginUrl);
// Shiro连接约束配置,即过滤链的定义
LinkedHashMap filterChainDefinitionMap = new LinkedHashMap<>();
// 对静态资源设置匿名访问
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/bootstrap/**", "anon");
filterChainDefinitionMap.put("/fonts/**", "anon");
filterChainDefinitionMap.put("/font-awesome/**", "anon");
filterChainDefinitionMap.put("/images/**", "anon");
filterChainDefinitionMap.put("/img/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/layer/**", "anon");
filterChainDefinitionMap.put("/ico/**", "anon");
// 不需要拦截的访问
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/do_login", "anon");
// 所有请求需要认证
filterChainDefinitionMap.put("/**", "authc");
bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return bean;
}
}
这里需要注意,需要把login和do_login这两个url的拦截给忽略掉,否则会被拦截,无法进行登录操作。
3:编写realm模块,继承自AuthorizingRealm类并重写其中的两个方法doGetAuthorizationInfo和doGetAuthenticationInfo两个方法,前者为权限管理处理方法,后者是处理身份认证的处理方法,也就是我们这次的重点,权限处理暂时不处理。
public class UserRealm extends AuthorizingRealm{
@Autowired
LoginService loginService;
/*
处理权限
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
return null;
}
/*
处理认证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken token1 = (UsernamePasswordToken) token;
String username = token1.getUsername();
String password = "";
if (token1.getPassword() != null){
password = new String(token1.getPassword());
}
User user = null;
System.out.println("用户名"+username+"密码"+password);
user = loginService.login(username,password);
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user,password,getName());
return info;
}
}
注意SimpleAuthenticationInfo类我们并没有把salt给上去,这里的密码加解密由我们自己是实现。
4:编写LoginController处理前端的登录操作
@Controller
public class LoginController {
@RequestMapping("/login")
public String loginPage(){
System.out.println("访问page");
return "login";
}
//ajax执行登录操作
@RequestMapping(value = "/do_login",method = RequestMethod.POST)
@ResponseBody
public AjaxResult login(String username, String password, HttpSession session){
System.out.println("访问lgon");
//再进行一遍MD5加密
String realPassword = MD5Utils.formPassToDbPass(password);
UsernamePasswordToken token = new UsernamePasswordToken(username,realPassword);
Subject subject = SecurityUtils.getSubject();
try{
subject.login(token);
session.setAttribute("user", subject.getPrincipal());
}catch (Exception e){
e.printStackTrace();
}
System.out.println("do_login"+" "+AjaxResult.success("登陆成功"));
return AjaxResult.success("登录成功");
}
@RequestMapping("/home")
public String index(){
return "index";
}
}
在login方法中,我们拿到的password是前端用户输完密码后的一次md5加密后的密码,前端加密下边会讲到,我们拿到这个password后再进行一边加密操作,然后生成token,交给subject去进行登录操作,最后返回我们封装好的AjaxResult,返回到前端为json类型,MD5Utils类如下:
public class MD5Utils {
private static final String SALT = "1a2b3c4d";
private static String md5(String pass){
return DigestUtils.md5Hex(pass);
}
//表单到后台的加密
public static String inputPassToFormPass(String inputPass){
String saltPass = "" + SALT.charAt(0) + SALT.charAt(2) + inputPass + SALT.charAt(5) + SALT.charAt(4);
return md5(saltPass);
}
//后台到数据库的加密
public static String formPassToDbPass(String formPass){
String saltPass = "" + SALT.charAt(0) + SALT.charAt(2) + formPass + SALT.charAt(5) + SALT.charAt(4);
return saltPass;
}
//综合两种加密
public static String inputPassToDbPass(String inputPass){
String realPass = formPassToDbPass(inputPassToFormPass(inputPass));
return realPass;
}
public static void main(String[] args) {
String realPass = inputPassToDbPass("1234");
System.out.println(realPass);
}
}
这里的盐我们写死在这里,实际项目中用该不会这么做,数据库中的密码也是我们提前用这个工具类手动生成的,这步可以写成一个注册的操作。
5:前端页面login.html的编写,需要注意的是,我们取消了form表单的submit提交功能,交由onclick的login()方法去处理。
login方法如下,拿到用户输入的密码,进行一次md5加密,然后使用ajax向后台请求。若返回结果成功,则进行页面跳转。
var salt = "1a2b3c4d";
function showLoading() {
var idx = layer.msg('处理中...', {icon: 16,shade: [0.5, '#f5f5f5'],scrollbar: false,offset: '0px', time:100000}) ;
return idx;
}
function login() {
showLoading();
var inputPass = $("#password").val();
var username = $("#username").val();
if (inputPass.length == 0 || username.length == 0){
alert("用户名或密码不允许为空");
layer.closeAll();
}else{
var str = salt.charAt(0)+salt.charAt(2) + inputPass +salt.charAt(5) + salt.charAt(4);
var password = md5(str);
$.ajax({
url:"/do_login",
type:"post",
data:{
username:username,
password:password
},
success:function (datas) {
layer.closeAll();
//根据返回的码判断结果
console.log(datas);
if(datas.code == 1){
layer.msg("登录成功");
window.location.href="/home";
}else{
layer.msg(datas.msg);
}
}
})
}
}
6:验证
当你没有进行登录的时候,进入任何界面都会先跳转到登录界面,登录完成后跳转到指定页面。
7:整个demo这里是下载链接,欢迎大家下载并指正。
https://download.csdn.net/download/fly_rice/10557890