shiro介绍:
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
三个核心组件:Subject, SecurityManager 和 Realms.
Subject:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。
Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。
SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。
Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。
从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。
Shiro内置了可以连接大量安全数据源(又名目录)的Realm,如LDAP、关系数据库(JDBC)、类似INI的文本配置资源以及属性文件等。如果缺省的Realm不能满足需求,你还可以插入代表自定义数据源的自己的Realm实现。
下面以springboot + shiro 为例,实现其认证授权过程。
1. 导入依赖(建议取maven仓库去找最新的依赖)
<!-- shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
2. 编写实体类(共 5张表,三个实体类)
2.1 User用户实体类
/**
* @Description : User用户实体类
*/
@Entity
@Table(name = "t_user_info")
public class UserInfo implements Serializable {
@Id
@GeneratedValue
private Integer uid;
@Column(unique =true)
private String username;//帐号
private String name;//名称(昵称或者真实姓名,不同系统不同定义)
private String password; //密码;
private String salt;//加密密码的盐
private byte state;//用户状态,0:创建未认证(比如没有激活,没有输入验证码等等)--等待验证的用户 , 1:正常状态,2:用户被锁定.
@ManyToMany(fetch= FetchType.EAGER)//立即从数据库中进行加载数据;
@JoinTable(name = "t_sys_user_role", joinColumns = { @JoinColumn(name = "uid") }, inverseJoinColumns ={@JoinColumn(name = "roleId") })
private List<SysRole> roleList;// 一个用户具有多个角色
public Integer getUid() {
return uid;
}
public void setUid(Integer uid) {
this.uid = uid;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getSalt() {
return salt;
}
public void setSalt(String salt) {
this.salt = salt;
}
public byte getState() {
return state;
}
public void setState(byte state) {
this.state = state;
}
public List<SysRole> getRoleList() {
return roleList;
}
public void setRoleList(List<SysRole> roleList) {
this.roleList = roleList;
}
/**
* 密码盐.
* @return
*/
public String getCredentialsSalt(){
return this.username+this.salt;
}
//重新对盐重新进行了定义,用户名+salt,这样就更加不容易被破解
}
2.2 角色实体类
/**
* @Description : 角色实体类
*/
@Entity
@Table(name = "t_sys_role")
public class SysRole {
@Id@GeneratedValue
private Integer id; // 编号
private String role; // 角色标识程序中判断使用,如"admin",这个是唯一的:
private String description; // 角色描述,UI界面显示使用
private Boolean available = Boolean.FALSE; // 是否可用,如果不可用将不会添加给用户
//角色 -- 权限关系:多对多关系;
@ManyToMany(fetch= FetchType.EAGER)
@JoinTable(name="t_sys_role_permission",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="permissionId")})
private List<SysPermission> permissions;
// 用户 - 角色关系定义;
@ManyToMany
@JoinTable(name="t_sys_user_role",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="uid")})
private List<UserInfo> userInfos;// 一个角色对应多个用户
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Boolean getAvailable() {
return available;
}
public void setAvailable(Boolean available) {
this.available = available;
}
public List<SysPermission> getPermissions() {
return permissions;
}
public void setPermissions(List<SysPermission> permissions) {
this.permissions = permissions;
}
public List<UserInfo> getUserInfos() {
return userInfos;
}
public void setUserInfos(List<UserInfo> userInfos) {
this.userInfos = userInfos;
}
}
2.3 权限 实体类
/**
* @Description : 权限 实体类
*/
@Entity
@Table(name = "t_sys_permission")
public class SysPermission implements Serializable {
@Id
@GeneratedValue
private Integer id;//主键.
private String name;//名称.
@Column(columnDefinition="enum('menu','button')")
private String resourceType;//资源类型,[menu|button]
private String url;//资源路径.
private String permission; //权限字符串,menu例子:role:*,button例子:role:create,role:update,role:delete,role:view
private Long parentId; //父编号
private String parentIds; //父编号列表
private Boolean available = Boolean.FALSE;
@ManyToMany
@JoinTable(name="t_sys_role_permission",joinColumns={@JoinColumn(name="permissionId")},inverseJoinColumns={@JoinColumn(name="roleId")})
private List<SysRole> roles;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getResourceType() {
return resourceType;
}
public void setResourceType(String resourceType) {
this.resourceType = resourceType;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getPermission() {
return permission;
}
public void setPermission(String permission) {
this.permission = permission;
}
public Long getParentId() {
return parentId;
}
public void setParentId(Long parentId) {
this.parentId = parentId;
}
public String getParentIds() {
return parentIds;
}
public void setParentIds(String parentIds) {
this.parentIds = parentIds;
}
public Boolean getAvailable() {
return available;
}
public void setAvailable(Boolean available) {
this.available = available;
}
public List<SysRole> getRoles() {
return roles;
}
public void setRoles(List<SysRole> roles) {
this.roles = roles;
}
}
3. 编写 shiro 核心类 ShiroConfiguration.java 和 MyshiroRealm.java
3.1 ShiroConfiguration.java
@Configuration
public class ShiroConfiguration {
/**
* Filter工厂,设置对应的过滤条件和跳转条件
* @param securityManager
* @return
*/
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
System.out.println("ShiroConfiguration.shirFilter()");
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//自定义拦截器
Map<String, Filter> shiroSessionFilterMap = new LinkedHashMap<>();
//map里面key值要为authc才能使用自定义的过滤器
shiroSessionFilterMap.put("authc", new XFormAuthenticationFilter());
//拦截器.
Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
// 配置不会被拦截的链接 顺序判断
filterChainDefinitionMap.put("/static/**", "anon");
//配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
filterChainDefinitionMap.put("/logout", "logout");
//<!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
//<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
//swagger接口权限 开放
filterChainDefinitionMap.put("/swagger-ui.html", "anon");
filterChainDefinitionMap.put("/swagger-resources", "anon");
filterChainDefinitionMap.put("/v2/api-docs", "anon");
filterChainDefinitionMap.put("/webjars/springfox-swagger-ui/**", "anon");
filterChainDefinitionMap.put("/**","authc");
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/login");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/index.html");
//未授权界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/403.html");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
shiroFilterFactoryBean.setFilters(shiroSessionFilterMap);
return shiroFilterFactoryBean;
}
/**
* 凭证匹配器
* (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了)
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5(""));
hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);//此处决定加密密码转化为16进制(与入库时保持一致),这一行决定hex还是base64
return hashedCredentialsMatcher;
}
/**
* 将自己的验证方式加入容器
* @return
*/
@Bean
public MyShiroRealm myShiroRealm(){
MyShiroRealm myShiroRealm = new MyShiroRealm();
myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return myShiroRealm;
}
/**
* 权限管理,配置主要是Realm的管理认证
* @return
*/
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
//配置自定义session管理,使用ehcache 或redis
securityManager.setSessionManager(sessionManager());
return securityManager;
}
/**
* 开启shiro aop注解支持.
* 使用代理方式;所以需要开启代码支持;
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
@Bean(name="simpleMappingExceptionResolver")
public SimpleMappingExceptionResolver createSimpleMappingExceptionResolver() {
SimpleMappingExceptionResolver r = new SimpleMappingExceptionResolver();
Properties mappings = new Properties();
mappings.setProperty("DatabaseException", "databaseError");//数据库异常处理
mappings.setProperty("UnauthorizedException","403");
r.setExceptionMappings(mappings); // None by default
r.setDefaultErrorView("error"); // No default
r.setExceptionAttribute("ex"); // Default is "exception"
//r.setWarnLogCategory("example.MvcLogger"); // No default
return r;
}
/**
* 配置保存sessionId的cookie
* 注意:这里的cookie 不是上面的记住我 cookie 记住我需要一个cookie session管理 也需要自己的cookie
* @return
*/
@Bean("sessionIdCookie")
public SimpleCookie sessionIdCookie(){
//这个参数是cookie的名称
SimpleCookie simpleCookie = new SimpleCookie("sid");
//setcookie的httponly属性如果设为true的话,会增加对xss防护的安全系数。它有以下特点:
//setcookie()的第七个参数
//设为true后,只能通过http访问,javascript无法访问
//防止xss读取cookie
simpleCookie.setHttpOnly(true);
simpleCookie.setPath("/");
//maxAge=-1表示浏览器关闭时失效此Cookie
simpleCookie.setMaxAge(-1);
return simpleCookie;
}
/**
* 配置会话管理器,设定会话超时及保存
* @return
*/
@Bean("sessionManager")
public SessionManager sessionManager() {
// DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
MySessionManager sessionManager = new MySessionManager();
Collection<SessionListener> listeners = new ArrayList<SessionListener>();
// //配置监听
// listeners.add(sessionListener());
// sessionManager.setSessionListeners(listeners);
sessionManager.setSessionIdCookie(sessionIdCookie());
// sessionManager.setSessionDAO(sessionDAO());
// sessionManager.setCacheManager(ehCacheManager());
//全局会话超时时间(单位毫秒),默认30分钟
sessionManager.setGlobalSessionTimeout(1800000);
//是否开启删除无效的session对象 默认为true
sessionManager.setDeleteInvalidSessions(true);
//是否开启定时调度器进行检测过期session 默认为true
sessionManager.setSessionValidationSchedulerEnabled(true);
//设置session失效的扫描时间, 清理用户直接关闭浏览器造成的孤立会话 默认为 1个小时
//设置该属性 就不需要设置 ExecutorServiceSessionValidationScheduler 底层也是默认自动调用ExecutorServiceSessionValidationScheduler
//相隔多久检查一次session的有效性
sessionManager.setSessionValidationInterval(1800000);
return sessionManager;
}
public class MySessionManager extends DefaultWebSessionManager {
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
String token = httpServletRequest.getHeader("token");
System.out.println("token:"+token);
if(!StringUtils.isEmpty(token)){
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "token");
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, token);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
return token;
}else{
return super.getSessionId(request, response);
}
}
}
}
3.2 MyShiroRealm.java
public class MyShiroRealm extends AuthorizingRealm {
@Resource
private IUserInfoService userInfoService;
/**
* 角色权限和对应权限添加
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("权限配置-->MyShiroRealm.doGetAuthorizationInfo()");
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
UserInfo userInfo = (UserInfo)principals.getPrimaryPrincipal();
//todo 从数据库查询账号拥有的角色
List<SysRole> roles = userInfoService.findRolesByUsername(userInfo.getUsername());
for(SysRole role:roles){
//todo 添加进来角色
authorizationInfo.addRole(role.getRole());
//todo 从数据库查询账号拥有的权限
List<SysPermission> permissions = userInfoService.findPermissionsByUsername(role.getId());
for(SysPermission p:permissions){
//todo 添加进来权限
authorizationInfo.addStringPermission(p.getPermission());
}
}
return authorizationInfo;
}
/**
*主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确。
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
//加这一步的目的是在Post请求的时候会先进认证,然后在到请求
if (token.getPrincipal() == null) {
return null;
}
System.out.println("MyShiroRealm.doGetAuthenticationInfo()");
//获取用户的输入的账号.
String username = (String)token.getPrincipal();
System.out.println(token.getCredentials());
//通过username从数据库中查找 User对象,如果找到,没找到.
//实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
UserInfo userInfo = userInfoService.findByUsername(username);
System.out.println("----->>userInfo="+userInfo);
if(userInfo == null){
return null;
}
System.out.println("sort : "+userInfo.getCredentialsSalt());
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
userInfo, //用户名
userInfo.getPassword(), //从数据库获得的密码
ByteSource.Util.bytes(userInfo.getCredentialsSalt()),//salt=username+salt 采用明文访问时,不需要此句
getName() //realm name
);
return authenticationInfo;
}
}
4.UserInfoMapper.xml (service就不贴出来了)
<?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.mapper.UserInfoMapper" >
<!--根据用户名查询用户信息-->
<select id="findByUsername" resultType="com.entity.UserInfo" parameterType="java.lang.String">
select
u.uid,
u.username,
u.`name`,
u.`password`,
u.salt,
u.state
from t_user_info as u
where username = #{username};
</select>
<!--根据用户名查询账号对应的角色名称-->
<select id="findRolesByUsername" resultType="com.entity.SysRole" parameterType="java.lang.String">
# select r.id,r.role,r.description,r.available
select
u.uid,
u.`name`,
u.`password`,
u.salt,
u.state,
u.username,
r.id,
r.available,
r.description,
r.role
from t_user_info as u
INNER JOIN t_sys_user_role as ur on ur.uid = u.uid
INNER JOIN t_sys_role as r on ur.roleId = r.id
WHERE u.username = #{username};
</select>
<!--根据roleId查询角色对应的权限名称-->
<select id="findPermissionsByUsername" resultType="com.entity.SysPermission" parameterType="java.lang.Integer">
SELECT
r.id,
r.available,
r.description,
r.role,
p.`name`,
p.permission,
p.url
FROM
t_sys_role as r
INNER JOIN t_sys_role_permission as rp ON r.id = rp.roleId
INNER JOIN t_sys_permission as p ON p.id = rp.permissionId
WHERE r.id=#{roleId}
</select>
<insert id="insertUser" parameterType="com.entity.UserInfo">
insert into t_user_info(
username,password,salt,state,name
)
values(
#{userInfo.username},#{userInfo.password},#{userInfo.salt},#{userInfo.state},#{userInfo.name}
)
</insert>
</mapper>
5. UserController.java
/**
* @Description : TODO用一句话描述此类的作用
*/
@Api(tags = "UserController", description = "登录注册API")
@Controller
public class UserController {
/**
* 日志
*/
private static final Logger logger = LoggerFactory.getLogger(UserController.class);
@Resource
private IUserInfoService userInfoService;
@ApiOperation(value = "index", notes = "index")
@RequestMapping(value = { "/", "/index" },method = RequestMethod.POST)
public String index() {
return "/index";
}
@ApiOperation(value = "登录", notes = "登录")
@ApiImplicitParams({
@ApiImplicitParam(name = "username", value = "用户名", paramType = "query", required = true, dataType = "String"),
@ApiImplicitParam(name = "password", value = "密码", paramType = "query", required = true, dataType = "String")
})
@RequestMapping(value="/login",method = {RequestMethod.GET, RequestMethod.POST},produces="text/html;charset=UTF-8")
public ResponseMsg userLogin(HttpServletRequest request, String username , String password) {
ResponseMsg responseMsg = new ResponseMsg();
Subject subject = SecurityUtils.getSubject();//获取当前主体
//(AOP拦截横切认证),处理登录失败信息
String exception = (String) request.getAttribute("shiroLoginFailure");
System.out.println("exception:"+exception);
String msg = "";
if (!subject.isAuthenticated()){
logger.error(_DateUtils.getCurrentTimeMillis(_DateUtils.sdFormatter)+"访问失败!-->请先登录!");
msg = "请先登录!";
responseMsg.setResult("请先登录!"+msg);
return responseMsg;
}
logger.error(_DateUtils.getCurrentTimeMillis(_DateUtils.sdFormatter)+"登录状态============>>>>>>> "+subject.isAuthenticated());
if (exception != null) {
if (UnknownAccountException.class.getName().equals(exception)) {
logger.error(_DateUtils.getCurrentTimeMillis(_DateUtils.sdFormatter)+"登录失败!-->账号不存在");
msg = "错误信息:账号不存在";
responseMsg.setResult("用户名为:"+username+"登录失败!失败原因:"+msg);
return responseMsg;
}else if (IncorrectCredentialsException.class.getName().equals(exception)) {
logger.error(_DateUtils.getCurrentTimeMillis(_DateUtils.sdFormatter)+"登录失败!-->密码不正确");
msg = "错误信息:密码不正确";
responseMsg.setResult("用户名为:"+username+"登录失败!失败原因:"+msg);
return responseMsg;
}else if ("kaptchaValidateFailed".equals(exception)) {
logger.error(_DateUtils.getCurrentTimeMillis(_DateUtils.sdFormatter)+"登录失败!-->验证码错误");
msg = "错误信息:验证码错误";
responseMsg.setResult("用户名为:"+username+"登录失败!失败原因:"+msg);
return responseMsg;
}else {
msg = "未知错误!";
System.out.println("else --> "+exception);
responseMsg.setResult("用户名为:"+username+"登录失败!失败原因:"+msg);
return responseMsg;
}
}
msg = "验证通过!";
logger.info(_DateUtils.getCurrentTimeMillis(_DateUtils.sdFormatter)+"登录成功!"+msg);
responseMsg.setResult("用户:"+username+" 登录成功!");
return responseMsg;
}
@ApiOperation(value = "注册", notes = "注册")
@RequestMapping(value = "/registered",method = RequestMethod.POST)
@ResponseBody
public ResponseMsg userRegistered(HttpServletRequest request, @RequestBody UserInfoVo user) {
ResponseMsg responseMsg = new ResponseMsg();
System.out.println("------用户注册-------");
//生成盐(部分,需要存入数据库中)
String sort=new SecureRandomNumberGenerator().nextBytes().toHex();
//将原始密码加盐(上面生成的盐),并且用md5算法加密2次,将最后结果存入数据库中
String result = new SimpleHash("MD5", user.getPassword(), user.getUsername()+sort, 2).toString();
System.out.println(sort+user.getUsername());
System.out.println(result);
user.setPassword(result);
user.setSalt(sort);
UserInfo userInfo = userVoToInfo(user);
Integer integer = userInfoService.insertUser(userInfo);
System.out.println("注册返回值为:" + integer);
if (integer > 0) {
responseMsg.setResult("注册成功!");
responseMsg.setResponseCode("200");
return responseMsg;
}else {
responseMsg.setResult("该用户名已存在,注册失败!");
responseMsg.setResult("500");
return responseMsg;
}
}
private UserInfo userVoToInfo(UserInfoVo userInfoVo){
UserInfo userInfo = new UserInfo();
userInfo.setUsername(userInfoVo.getUsername());
userInfo.setPassword(userInfoVo.getPassword());
userInfo.setName(userInfoVo.getName());
userInfo.setSalt(userInfoVo.getSalt());
userInfo.setState(userInfoVo.getState());
userInfo.setRoleList(userInfoVo.getRoleList());
return userInfo;
}
@ApiOperation(value = "根据用户名查询账号对应的角色名称", notes = "根据用户名查询账号对应的角色名称")
@ApiImplicitParam(name = "username", value = "String", paramType = "query", required = true, dataType = "String")
@RequestMapping(value = "/findRolesByUsername",method = RequestMethod.POST)
public List<SysRole> findRolesByUsername(String username){
List<SysRole> rolesByUsername = userInfoService.findRolesByUsername(username);
return rolesByUsername;
}
@ApiOperation(value = "根据roleId查询角色对应的权限名称", notes = "根据roleId查询角色对应的权限名称")
@ApiImplicitParam(name = "roleId", value = "int", paramType = "query", required = true, dataType = "int")
@RequestMapping(value = "/findPermissionsByUsername",method = RequestMethod.POST)
public List<SysPermission> findPermissionsByUsername(Integer roleId){
List<SysPermission> permissionsByUsername = userInfoService.findPermissionsByUsername(roleId);
return permissionsByUsername;
}
}
5.1 用到了与前端交互的 VO 类,简单实现一个UserInfoVo.java
/**
* @Description : 前端交互的 UserVo 类
*/
public class UserInfoVo implements Serializable {
private Integer uid;
private String username;//帐号
private String name;//名称(昵称或者真实姓名,不同系统不同定义)
private String password; //密码;
private String salt;//加密密码的盐
private byte state;//用户状态,0:创建未认证(比如没有激活,没有输入验证码等等)--等待验证的用户 , 1:正常状态,2:用户被锁定.
private List<SysRole> roleList;// 一个用户具有多个角色
public Integer getUid() {
return uid;
}
public void setUid(Integer uid) {
this.uid = uid;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getSalt() {
return salt;
}
public void setSalt(String salt) {
this.salt = salt;
}
public byte getState() {
return state;
}
public void setState(byte state) {
this.state = state;
}
public List<SysRole> getRoleList() {
return roleList;
}
public void setRoleList(List<SysRole> roleList) {
this.roleList = roleList;
}
/**
* 密码盐.
* @return
*/
public String getCredentialsSalt(){
return this.username+this.salt;
}
//重新对盐重新进行了定义,用户名+salt,这样就更加不容易被破解
}
6.编写简单前端登陆页面 login.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Login</title>
</head>
<body>
<form action="/login" method="post">
<ul>
<li>姓 名:<input type="text" name="username" /> </li>
<li>密 码:<input type="text" name="password" /> </li>
<!--<li>验证码:<input type="text" name="validateCode" />-->
<!--<img id="validateCodeImg" src=<%=basePath%>/validateCode"/>-->
<!--<a href="#" onclick="javascript:reloadValidateCode();">看不清?</a></li>-->
<li><input type="submit" value="确认" /> </li>
</ul>
</form>
</body>
</html>
7.准备工作(为了简单测试,就直接在数据库中添加 用户 角色 权限信息,及其中间表信息)
7.1 注册两个用户admin 和 test
7.2 数据库 t_sys_role 表 加入两条角色信息
7.3 权限表 添加权限信息
7.4 用户角色表
7.5 角色权限表
8.登录认证测试
登录成功之后跳转index.html页面
登录失败,返回对应失败信息
9.权限检验测试
测试之前需要说明的是,要判断账号是否有对应权限,需要在对应接口添加 @RequiresPermissions,来确认该用户是否具有对应权限,当然前提是在ShiroConfiguration.java开启注解支持
@RequiresPermissions(userInfo:add)
9.1 test用户没有 userInfo:add 权限
结果:没有对应权限,跳转到403.html页面。
9.2 admin用户有 userInfo:add 权限
结果:可以正常访问