一、pom中引入shiro的依赖(省去多余代码)
<properties>
<shiro-version>1.2.5shiro-version>
<extras-shiro-version>1.2.1extras-shiro-version>
properties>
<dependencies>
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-springartifactId>
<version>${shiro-version}version>
dependency>
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-ehcacheartifactId>
<version>${shiro-version}version>
dependency>
<dependency>
<groupId>com.github.theborakompanionigroupId>
<artifactId>thymeleaf-extras-shiroartifactId>
<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"); //配置退出
//
//
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 roles = new ArrayList(); //角色
List prems = new ArrayList(); //权限
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
<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>
<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。