Spring boot后台搭建二集成Shiro实现用户验证
上一篇文章中介绍了Shiro 查看
将Shiro集成到spring boot的步骤:
(1)定义一个ShiroConfig,配置SecurityManager Bean,SecurityManager为Shiro的安全管理器,管理着所有Subject
(2)在ShiroConfig中配置ShiroFilterFactoryBean,其为Shiro过滤器工厂类,依赖于SecurityManager
(3)自定义Realm实现,Realm包含doGetAuthorizationInfo()
和doGetAuthenticationInfo()
方法
实现下用户认证:
没登录跳转到登录页面;登录后跳转到首页面;注销后,跳转到登录页面
1.引入依赖
引入Shiro和thymeleaf依赖
<!-- thymeleaf --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!-- shiro-spring --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency>
2.ShiroConfig
定义一个Shiro配置类,ShiroConfig
package com.sfn.bms.common.shiro; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.*; /** * Shiro 配置类 */ @Configuration public class ShiroConfig { @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // 设置securityManager shiroFilterFactoryBean.setSecurityManager(securityManager); // 登录的url shiroFilterFactoryBean.setLoginUrl("/login"); // 登录成功后跳转的url shiroFilterFactoryBean.setSuccessUrl("/index"); // 未授权url shiroFilterFactoryBean.setUnauthorizedUrl("/403"); LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); // 定义filterChain,静态资源不拦截 filterChainDefinitionMap.put("/css/**", "anon"); filterChainDefinitionMap.put("/js/**", "anon"); filterChainDefinitionMap.put("/fonts/**", "anon"); filterChainDefinitionMap.put("/img/**", "anon"); // druid数据源监控页面不拦截 filterChainDefinitionMap.put("/druid/**", "anon"); // 配置退出过滤器,其中具体的退出代码Shiro已经替我们实现了 filterChainDefinitionMap.put("/logout", "logout"); filterChainDefinitionMap.put("/", "anon"); // 除上以外所有url都必须认证通过才可以访问,未通过认证自动访问LoginUrl filterChainDefinitionMap.put("/**", "authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(myShiroRealm()); return securityManager; } @Bean public MyShiroRealm myShiroRealm() { MyShiroRealm shiroRealm = new MyShiroRealm(); return shiroRealm; } }
anon
、authc
等为Shiro为实现的过滤器
3.Realm
Realm进行实现,然后注入到SecurityManager中
自定义Realm实现只需继承AuthorizingRealm类,然后实现doGetAuthorizationInfo()和doGetAuthenticationInfo()方法即可
用户认证只实现doGetAuthenticationInfo()
package com.sfn.bms.common.shiro; import com.sfn.bms.system.model.User; import com.sfn.bms.system.service.UserService; import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** * 自定义实现 ShiroRealm,包含认证和授权两大模块 */ @Component("shiroRealm") public class MyShiroRealm extends AuthorizingRealm { @Autowired private UserService userService; /** * 授权模块,获取用户角色和权限 * * @param principal principal * @return AuthorizationInfo 权限信息 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) { return null; } /** * 用户认证 * * @param token AuthenticationToken 身份认证 token * @return AuthenticationInfo 身份认证信息 * @throws AuthenticationException 认证相关异常 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { // 获取用户输入的用户名和密码 String account = (String) token.getPrincipal(); String password = new String((char[]) token.getCredentials()); // 通过用户名到数据库查询用户信息 User user = userService.findByAccount(account); if (user == null) { throw new UnknownAccountException("用户名或密码错误!"); } if (!password.equals(user.getPassword())) { throw new IncorrectCredentialsException("用户名或密码错误!"); } if (user.getStatus().equals("0")) { throw new LockedAccountException("账号已被锁定,请联系管理员!"); } SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, getName()); return info; } }
4.数据层
数据表User
实体类User
package com.sfn.bms.system.model; import javax.persistence.*; public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Short id; /** * 账号 */ private String account; /** * 密码 */ private String password; /** * 邮箱 */ private String email; /** * 状态 1-正常,0-禁用,-1-删除 */ private Boolean status; /** * 添加时间 */ @Column(name = "create_time") private Integer createTime; /** * 上次登陆时间 */ @Column(name = "last_login_time") private Integer lastLoginTime; /** * 上次登录IP */ @Column(name = "last_login_ip") private String lastLoginIp; /** * 登陆次数 */ @Column(name = "login_count") private Integer loginCount; /** * @return id */ public Short getId() { return id; } /** * @param id */ public void setId(Short id) { this.id = id; } /** * 获取账号 * * @return account - 账号 */ public String getAccount() { return account; } /** * 设置账号 * * @param account 账号 */ public void setAccount(String account) { this.account = account == null ? null : account.trim(); } /** * 获取密码 * * @return password - 密码 */ public String getPassword() { return password; } /** * 设置密码 * * @param password 密码 */ public void setPassword(String password) { this.password = password == null ? null : password.trim(); } /** * 获取邮箱 * * @return email - 邮箱 */ public String getEmail() { return email; } /** * 设置邮箱 * * @param email 邮箱 */ public void setEmail(String email) { this.email = email == null ? null : email.trim(); } /** * 获取状态 1-正常,0-禁用,-1-删除 * * @return status - 状态 1-正常,0-禁用,-1-删除 */ public Boolean getStatus() { return status; } /** * 设置状态 1-正常,0-禁用,-1-删除 * * @param status 状态 1-正常,0-禁用,-1-删除 */ public void setStatus(Boolean status) { this.status = status; } /** * 获取添加时间 * * @return create_time - 添加时间 */ public Integer getCreateTime() { return createTime; } /** * 设置添加时间 * * @param createTime 添加时间 */ public void setCreateTime(Integer createTime) { this.createTime = createTime; } /** * 获取上次登陆时间 * * @return last_login_time - 上次登陆时间 */ public Integer getLastLoginTime() { return lastLoginTime; } /** * 设置上次登陆时间 * * @param lastLoginTime 上次登陆时间 */ public void setLastLoginTime(Integer lastLoginTime) { this.lastLoginTime = lastLoginTime; } /** * 获取上次登录IP * * @return last_login_ip - 上次登录IP */ public String getLastLoginIp() { return lastLoginIp; } /** * 设置上次登录IP * * @param lastLoginIp 上次登录IP */ public void setLastLoginIp(String lastLoginIp) { this.lastLoginIp = lastLoginIp == null ? null : lastLoginIp.trim(); } /** * 获取登陆次数 * * @return login_count - 登陆次数 */ public Integer getLoginCount() { return loginCount; } /** * 设置登陆次数 * * @param loginCount 登陆次数 */ public void setLoginCount(Integer loginCount) { this.loginCount = loginCount; } }
UserService
package com.sfn.bms.system.service; import com.sfn.bms.common.service.IService; import com.sfn.bms.system.model.User; public interface UserService extends IService<User> { User findByAccount(String account); }
UserServiceImpl
package com.sfn.bms.system.service.impl; import com.sfn.bms.common.service.impl.BaseService; import com.sfn.bms.system.model.User; import com.sfn.bms.system.service.UserService; import org.springframework.stereotype.Repository; import tk.mybatis.mapper.entity.Example; import java.util.List; @Repository("userService") public class UserServiceImpl extends BaseService<User> implements UserService { @Override public User findByAccount(String account) { Example example = new Example(User.class); example.createCriteria().andCondition("lower(account)=", account.toLowerCase()); List<User> list = this.selectByExample(example); return list.isEmpty() ? null : list.get(0); } }
UserMapper.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.sfn.bms.system.mapper.UserMapper"> <resultMap id="BaseResultMap" type="com.sfn.bms.system.model.User"> <!-- WARNING - @mbg.generated --> <id column="id" jdbcType="SMALLINT" property="id" /> <result column="account" jdbcType="VARCHAR" property="account" /> <result column="password" jdbcType="CHAR" property="password" /> <result column="email" jdbcType="VARCHAR" property="email" /> <result column="status" jdbcType="BIT" property="status" /> <result column="create_time" jdbcType="INTEGER" property="createTime" /> <result column="last_login_time" jdbcType="INTEGER" property="lastLoginTime" /> <result column="last_login_ip" jdbcType="VARCHAR" property="lastLoginIp" /> <result column="login_count" jdbcType="INTEGER" property="loginCount" /> </resultMap> </mapper>
5.前端页面
login.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" th:href="@{/css/login.css}" type="text/css"> <link rel="stylesheet" th:href="@{css/iCheck/minimal/blue.css}" type="text/css"> <link rel="stylesheet" th:href="@{css/app.css}" type="text/css"> <script th:src="@{/js/jquery.min.js}"></script> </head> <body> <div class="login-page"> <!-- Form--> <div class="form"> <div class="form-toggle"></div> <div class="form-panel one"> <div class="form-header"> <h1>账户登录</h1> </div> <div class="form-content"> <div class="form-group"> <label>用户名</label> <input type="text" name="account" /> </div> <div class="form-group"> <label>密码</label> <input type="password" name="password" /> </div> <div class="form-group"> <button onclick="login()" id="loginButton">登录</button> </div> </div> </div> </div> </div> </body> <script th:inline="javascript"> var ctx = [[@{/}]]; function login() { var account= $("input[name='account']").val(); var password = $("input[name='password']").val(); $.ajax({ type: "post", url: ctx + "login", data: {"account": account,"password": password}, dataType: "json", success: function (r) { if (r.code == 0) { location.href = ctx + 'index'; } else { alert(r.msg); } } }); } </script> </html>
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <p>你好![[${user.account}]]</p> <a th:href="@{/logout}">注销</a> </body> </html>
6.Controller方法
LoginController
package com.sfn.bms.system.controller; import com.sfn.bms.common.domian.ResponseBo; import com.sfn.bms.common.util.MD5Utils; import com.sfn.bms.system.model.User; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; import org.apache.shiro.session.Session; import org.apache.shiro.subject.Subject; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller public class LoginController { @GetMapping("/login") public String login() { return "login"; } @PostMapping("/login") @ResponseBody public ResponseBo login(String account, String password) { // 密码MD5加密 password = MD5Utils.encrypt(account, password); System.out.println(password); UsernamePasswordToken token = new UsernamePasswordToken(account, password); // 获取Subject对象 Subject subject = SecurityUtils.getSubject(); try { subject.login(token); return ResponseBo.ok(); } catch (UnknownAccountException e) { return ResponseBo.error(e.getMessage()); } catch (IncorrectCredentialsException e) { return ResponseBo.error(e.getMessage()); } catch (LockedAccountException e) { return ResponseBo.error(e.getMessage()); } catch (AuthenticationException e) { return ResponseBo.error("认证失败!"); } } @RequestMapping("/") public String redirectIndex() { return "redirect:/index"; } @RequestMapping("/index") public String index(Model model) { // 登录成后,即可通过Subject获取登录的用户信息 User user = (User) SecurityUtils.getSubject().getPrincipal(); model.addAttribute("user", user); return "index"; } }
7.测试
启动项目
浏览器打开
http://localhost:8080/
http://localhost:8080/index
回跳转到http://localhost:8080/login
输入用户名super密码123456
点注销,根据ShiroConfig的配置filterChainDefinitionMap.put("/logout", "logout"),
Shiro会自动帮我们注销用户信息,并重定向到/
路径
相关代码 地址