您的位置:首页 > 编程语言 > Java开发

Spring boot 整合shiro 实现登陆验证

2017-12-08 15:55 981 查看
一、pom中引入shiro的依赖(省去多余代码)

<properties>
<shiro-version>1.2.5</shiro-version>
<extras-shiro-version>1.2.1</extras-shiro-version>
</properties>
<dependencies>
<!-- shiro spring -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro-version}</version>
</dependency>
<!-- shiro ehcache (shiro缓存)-->
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-ehcache -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>${shiro-version}</version>
</dependency>
<!-- shiro thymeleaf-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>${extras-shiro-version}</version>
</dependency>
</dependencies>


三个依赖组件,三种用途,已在注释中标注清楚。

二、shiro 配置 ShiroConfiguration,取代以往的 xml 文件,这里使用java 配置。

@Configuration
public class ShiroConfiguration {
//...
}


1、配置过滤器 shiroFilter

@Bean(name="shiroFilter")
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){

ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);

shiroFilterFactoryBean.setLoginUrl("/login");//登录连接
shiroFilterFactoryBean.setSuccessUrl("/index");//登录成功后跳转的连接
shiroFilterFactoryBean.setUnauthorizedUrl("/pages/403"); //未授权跳转页面

//定义shiro过滤链
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
//配置退出
filterChainDefinitionMap.put("/logout", "logout"); //配置退出
// <!-- 过滤链定义,从上向下顺序执行,/**放在最下面,过滤链的最后一关,表示除去以上各环节,剩余url的都需要验证 -->
// <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
filterChainDefinitionMap.put("/js/**","anon");
filterChainDefinitionMap.put("/css/**","anon");
filterChainDefinitionMap.put("/register","anon");
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}


2、配置安全管理器 SecurityManager

@Bean
public SecurityManager securityManager(){
//使用默认的安全管理器
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(securityRealm());  //加入自定义的安全领域
return securityManager;
}

@Bean
public SecurityRealm securityRealm(){
SecurityRealm securityRealm = new SecurityRealm();
securityRealm.setCredentialsMatcher(hashedCredentialsMatcher());//凭证匹配器
securityRealm.setCachingEnabled(false);//不使用缓存
return securityRealm;
}

@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher  hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");//使用MD5散列算法
hashedCredentialsMatcher.setHashIterations(1);//散列次数,这里等于1次MD5
hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);  //散列后密码为16进制,要与生成密码时一致。false 表示Base64编码
return hashedCredentialsMatcher;
}


三、自定义安全领域 SecurityRealm ,继承shiro的抽象类 AuthorizingRealm,重写认证和授权两个方法。本节主要内容是登陆验证,我们只详细实现认证环节。

public class SecurityRealm extends AuthorizingRealm {
/**
* 授权
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String currentUserName = (String)principalCollection.getPrimaryPrincipal();
List<String> roles = new ArrayList<String>();  //角色
List<String> prems = new ArrayList<String>(); //权限
roles.add("baidu");
roles.add("google");
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.addRoles(roles);
authorizationInfo.addStringPermissions(prems);
return authorizationInfo;
}

/**
* 认证,验证当前登录的Subject
* LoginController.login 方法中调用
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

SecurityUtils.getSubject().getSession().getId(),AuthenticationInfo.class);
String userName = (String)authenticationToken.getPrincipal();
//为了测试通过,这里暂时写死, user: admin password: 123456
// UserVo user = loginClient.selectUserByName(userName);
UserVo user = new UserVo();
user.setName(userName);
user.setPassword(new Md5Hash("123456").toHex()); //与SecurityManager加密方式一致,使用一次散列,并转为16进制,验证方能通过。
if(user == null){
throw new UnknownAccountException();//没找到帐号
}
//交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
"admin", //用户名
user.getPassword(), //密码
getName()  //realm name
);
return authenticationInfo;
}
}


四、控制器Controller

@PostMapping("/login")
public String login(Model model, @Valid UserVo userVo, BindingResult bindingResult, RedirectAttributes redirectAttributes){
if(bindingResult.hasErrors()){
model.addAttribute("error",bindingResult.getFieldError().getDefaultMessage());
return "login";
}
String userName = userVo.getName();
UsernamePasswordToken token = new UsernamePasswordToken(userVo.getName(), userVo.getPassword());
Subject currentUser = SecurityUtils.getSubject();

try {
currentUser.login(token);
}catch (IncorrectCredentialsException ice){
logger.info("对用户【" + userName +"】进行登录验证,验证未通过,错误的凭证!");
redirectAttributes.addFlashAttribute("error","用户名或密码不正确!");
}catch(UnknownAccountException uae){
logger.info("对用户【" + userName +"】进行登录验证,验证未通过,未知账户!");
redirectAttributes.addFlashAttribute("error","未知账户!");
}catch(LockedAccountException lae){
logger.info("对用户【" + userName +"】进行登录验证,验证未通过,账户锁定!");
redirectAttributes.addFlashAttribute("error","账户已锁定!");
}catch(ExcessiveAttemptsException eae){
logger.info("对用户【" + userName +"】进行登录验证,验证未通过,错误次数太多!");
redirectAttributes.addFlashAttribute("error","用户名或密码错误次数太多!");
}catch(AuthenticationException ae){
logger.info("对用户【" + userName +"】进行登录验证,验证未通过,堆栈轨迹如下:!");
ae.printStackTrace();
redirectAttributes.addFlashAttribute("error","用户名或密码不正确!");
}

if(currentUser.isAuthenticated()){
model.addAttribute("name",userName);
return "index";
}else{
token.clear();
return "redirect:/login";
}
}


五、前端页面,任意写一个登陆表单即可验证我这里使用 thymeleaf

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
<title>登录</title>
<style>
body{
margin-left:auto;
margin-right:auto;
margin-TOP:100PX;
width:20em;
}
</style>
</head>
<body>
<form th:action="@{/login}" method="post">
<div>
<!--/*@thymesVar id="error" type=""*/-->
<span id="basic-addon0"> </span>
<span style="font-size: 12px;color: red" th:text="${error}" aria-describedby="basic-addon0"></span>
<br />
</div>
<div>
<span id="basic-addon1">@</span>
<input id="user_name" name="name" type="text" placeholder="用户名" aria-describedby="basic-addon1" />

</div>
<br />
<div>
<span id="basic-addon2">@</span>
<input id="password" name="password" type="password" placeholder="密码" aria-describedby="basic-addon2" />
</div>
<br />
<button type="submit" style="width:190px;">登 录</button>
</form>
</body>
</html>


六、封装接收请求参数的UserVo 实体类。

public class UserVo {

@NotEmpty(message="用户名不能为空!")
private String name;
@Size(min=6,max=10,message = "密码长度必须6到10位")
private String password;

//...省去getter setter 方法
}


github源码:Clone with HTTPS:https://github.com/libinbin8130/personal-websites.git

说明:本章节内容所在项目路径(pw-platform/pf-front/pf-web),由于是多模块项目,pf-web的构建依赖于跟目录pf-front的pom。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: