转:http://412887952-qq-com.iteye.com/blog/2299777
(1). Shiro简单介绍
Shiro是Apache下的一个开源项目,我们称之为Apache Shiro。它是一个很易用与Java项目的的安全框架,提供了认证、授权、加密、会话管理,与 Spring Security 一样都是做一个权限的安全框架,但是与Spring Security 相比,在于 Shiro 使用了比较简单易懂易于使用的授权方式。
Apache Shiro 的三大核心组件
- Subject 当前用户操作
- SecurityManager 用于管理所有的Subject
- Realms 用于进行权限信息的验证,也是我们需要自己实现的。
我们需要实现Realms的Authentication 和 Authorization。其中 Authentication 是用来验证用户身份,Authorization 是授权访问控制,用于对用户进行的操作授权,证明该用户是否允许进行当前操作,如访问某个链接,某个资源文件等。
Apache Shiro 核心通过 Filter 来实现,就好像SpringMvc 通过DispachServlet 来主控制一样。
既然是使用 Filter 一般也就能猜到,是通过URL规则来进行过滤和权限校验,所以我们需要定义一系列关于URL的规则和访问权限。
另外我们可以通过Shiro 提供的会话管理来获取Session中的信息。Shiro 也提供了缓存支持,使用 CacheManager 来管理。
官方网站:http://shiro.apache.org/
完整架构图:
Shiro是很强大的一个安全框架,这里只是抛装引玉下,还有很多的需要大家自己去学习Shiro。
(2). 集成Shiro核心分析
集成Shiro的话,我们需要知道Shiro框架大概的一些管理对象。
第一:ShiroFilterFactory,Shiro过滤器工厂类,具体的实现类是:ShiroFilterFactoryBean,此实现类是依赖于SecurityManager安全管理器。
第二:SecurityManager,Shiro的安全管理,主要是身份认证的管理,缓存管理,cookie管理,所以在实际开发中我们主要是和SecurityManager进行打交道的,ShiroFilterFactory主要配置好了Filter就可以了。当然SecurityManager并进行身份认证缓存的实现,我们需要进行对应的编码然后进行注入到安全管理器中。
第三:Realm,用于身份信息权限信息的验证。
第四:其它的就是缓存管理,记住登录之类的,这些大部分都是需要自己进行简单的实现,然后注入到SecurityManager让Shiro的安全管理器进行管理就好了。
(3). 无Shiro的Spring Boot
我们先编写一个无Shiro的简单的框架,在这个框架中我们可以访问到index,login,userInfo,userInfoAdd。
这个步骤对于有Spring Boot基础的就应该很简单了,在这里简单的介绍下:
(a) 新建一个maven java project,取名为spring-boot-shiro1
(b) 在pom.xml中引入基本依赖,在这里还没有引入shiro等的依赖:
4.0.0
com.example
spring-boot-shiro1
0.0.1-SNAPSHOT
jar
UTF-8
org.springframework.boot
spring-boot-starter-parent
1.4.0.RELEASE
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-thymeleaf
org.springframework.boot
spring-boot-devtools
true
(c) 编写网页文件:
index.html,login.html,userInfo.html,userInfoAdd.html
这个文件存在在src/main/resouces/templates, 这几个文件中都是简单的代码,只有登录界面中有账号和密码:
Insert title here
index
login.html
Insert title here
错误信息:
userInfo.html,
Insert title here
用户查询界面
userInfoAdd.html
(d)编写启动类
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
(e)编写HomeController类
package com.example.controller;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class HomeController {
@RequestMapping({ "/", "index" })
public String index() {
return "/index";
}
@RequestMapping(value = "/login", method = RequestMethod.GET)
public String login() {
return "/login";
}
}
集成shiro大概分这么一个步骤:
(a) pom.xml中添加Shiro依赖;
(b) 注入Shiro Factory和SecurityManager。
(c) 身份认证
(d) 权限控制
(a) pom.xml中添加Shiro依赖;
要使用Shiro进行权限控制,那么很明显的就需要添加对Shiro的依赖包,在pom.xml中加入如下配置:
org.apache.shiro
shiro-spring
1.2.3
(b) 注入Shiro Factory和SecurityManager。
新建ShiroConfiguration.java
package com.example.config.shiro;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ShiroConfiguration {
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
System.out.println("ShiroConfiguration.shiroFilter()");
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必须设置SecuritManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 拦截器
Map filterChainDefinitionMap = new LinkedHashMap();
// 配置退出过滤器,其中的具体代码Shiro已经替我们实现了
filterChainDefinitionMap.put("/logout", "logout");
// :这是一个坑呢,一不小心代码就不好使了;
//
filterChainDefinitionMap.put("/**", "authc");
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/login");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/index");
// 未授权界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
return securityManager;
}
}
这里说下:ShiroFilterFactory中已经由Shiro官方实现的过滤器:
Shiro内置的FilterChain
Filter Name |
Class |
anon |
org.apache.shiro.web.filter.authc.AnonymousFilter |
authc |
org.apache.shiro.web.filter.authc.FormAuthenticationFilter |
authcBasic |
org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter |
perms |
org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter |
port |
org.apache.shiro.web.filter.authz.PortFilter |
rest |
org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter |
roles |
org.apache.shiro.web.filter.authz.RolesAuthorizationFilter |
ssl |
org.apache.shiro.web.filter.authz.SslFilter |
user |
org.apache.shiro.web.filter.authc.UserFilter |
UserInfo.java、SysRole.java、SysPermission.java至于之前的关联表我们使用JPA进行自动生成。
org.springframework.boot
spring-boot-starter-data-jpa
mysql
mysql-connector-java
配置src/main/resouces/application.properties配置数据库和jpa(application.properties新建一个即可):
########################################################
###datasource
########################################################
spring.datasource.url = jdbc:mysql://localhost:3306/test
spring.datasource.username = root
spring.datasource.password = root
spring.datasource.driverClassName = com.mysql.jdbc.Driver
spring.datasource.max-active=20
spring.datasource.max-idle=8
spring.datasource.min-idle=8
spring.datasource.initial-size=10
########################################################
### Java Persistence Api
########################################################
# Specify the DBMS
spring.jpa.database = MYSQL
# Show or not log for each sql query
spring.jpa.show-sql = true
# Hibernate ddl auto (create, create-drop, update)
spring.jpa.hibernate.ddl-auto = update
# Naming strategy
#[org.hibernate.cfg.ImprovedNamingStrategy | org.hibernate.cfg.DefaultNamingStrategy]
spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.DefaultNamingStrategy
# stripped before adding them to the entity manager)
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
UserInfo.java
package com.example.domain;
import java.io.Serializable;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
/**
* 用户信息.
* @author Administrator
*
*/
@Entity
public class UserInfo implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue
private long uid;// 用户id
@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 = "SysUserRole", joinColumns = { @JoinColumn(name = "uid") }, inverseJoinColumns = {
@JoinColumn(name = "roleId") })
private List roleList;// 一个用户具有多个角色
public long getUid() {
return uid;
}
public void setUid(long 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 getRoleList() {
return roleList;
}
public void setRoleList(List roleList) {
this.roleList = roleList;
}
/**
* 密码盐.
*
* @return
*/
public String getCredentialsSalt() {
return this.username + this.salt;
}
@Override
public String toString() {
return "UserInfo [uid=" + uid + ", username=" + username + ", name=" + name + ", password=" + password
+ ", salt=" + salt + ", state=" + state + "]";
}
}
在这里salt主要是用来进行密码加密的,当然也可以使用明文进行编码测试,实际开发中还是建议密码进行加密。
getCredentialsSalt()
这个方法重新对盐重新进行了定义,用户名+salt,这样就更加不容易被破解了
package com.example.domain;
import java.io.Serializable;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
/**
* 系统角色实体类;
*
* @author Administrator
*
*/
@Entity
public class SysRole implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue
private Long id; // 编号
private String role; // 角色标识程序中判断使用,如"admin",这个是唯一的:
private String description; // 角色描述,UI界面显示使用
private Boolean available = Boolean.FALSE; // 是否可用,如果不可用将不会添加给用户
// 角色 -- 权限关系:多对多关系;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "SysRolePermission", joinColumns = { @JoinColumn(name = "roleId") }, inverseJoinColumns = {
@JoinColumn(name = "permissionId") })
private List permissions;
// 用户 - 角色关系定义;
@ManyToMany
@JoinTable(name = "SysUserRole", joinColumns = { @JoinColumn(name = "roleId") }, inverseJoinColumns = {
@JoinColumn(name = "uid") })
private List userInfos;// 一个角色对应多个用户
public List getUserInfos() {
return userInfos;
}
public void setUserInfos(List userInfos) {
this.userInfos = userInfos;
}
public Long getId() {
return id;
}
public void setId(Long 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 getPermissions() {
return permissions;
}
public void setPermissions(List permissions) {
this.permissions = permissions;
}
@Override
public String toString() {
return "SysRole [id=" + id + ", role=" + role + ", description=" + description + ", available=" + available
+ ", permissions=" + permissions + "]";
}
}
SysPermission.java
package com.example.domain;
import java.io.Serializable;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
/**
* 权限实体类;
*
*/
@Entity
public class SysPermission implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue
private long 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(fetch = FetchType.LAZY)
// @JoinTable(name = "SysRolePermission", joinColumns = { @JoinColumn(name = "permissionId") }, inverseJoinColumns = {
// @JoinColumn(name = "roleId") })
// private List roles;
public long getId() {
return id;
}
public void setId(long 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 getRoles() {
// return roles;
// }
//
// public void setRoles(List roles) {
// this.roles = roles;
// }
@Override
public String toString() {
return "SysPermission [id=" + id + ", name=" + name + ", resourceType=" + resourceType + ", url=" + url
+ ", permission=" + permission + ", parentId=" + parentId + ", parentIds=" + parentIds + ", available="
+ available + "]";
}
}
mysql> show tables;
+---------------------+
| Tables_in_test |
+---------------------+
| sys_permission |
| sys_role |
| sys_role_permission |
| sys_user_role |
| user_info |
+---------------------+
5 rows in set (0.08 sec)
mysql>
sql
INSERT INTO `sys_permission`(name,parent_id,parent_ids,available,permission,resource_type,url)
VALUES ('用户管理',0,'0/' ,1,'userInfo:view', 'menu', 'userInfo/userList');
INSERT INTO `sys_permission`(name,parent_id,parent_ids,available,permission,resource_type,url)
VALUES ('用户添加',1,'0/1',1,'userInfo:add', 'button', 'userInfo/userAdd');
INSERT INTO `sys_permission`(name,parent_id,parent_ids,available,permission,resource_type,url)
VALUES ('用户删除',1,'0/1',1,'userInfo:del', 'button', 'userInfo/userDel');
INSERT INTO `sys_role`(available,description,role) VALUES (1,'管理员','admin');
INSERT INTO `sys_role`(available,description,role) VALUES (1,'VIP会员','vip');
INSERT INTO `sys_role_permission`(permission_id,role_id) VALUES ('1', '1');
INSERT INTO `sys_role_permission`(permission_id,role_id) VALUES ('2', '1');
INSERT INTO `user_info`(name,password,salt,state,username) VALUES ('管理员', 'd3c59d25033dbf980d29554025c23a75', '8d78869f470951332959580424d4bf4f', '0', 'admin');
INSERT INTO `sys_user_role`(uid,role_id) VALUES (1,1);
INSERT INTO `sys_user_role`(uid,role_id) VALUES (1,2);
这时候数据都准备完毕了,那么接下来就应该编写Repository进行访问数据了
package com.example.repository;
import org.springframework.data.repository.CrudRepository;
import com.example.domain.UserInfo;
/**
* UserInfo持久化类
*
* @author Administrator
*
*/
public interface UserInfoRepository extends CrudRepository {
/** 通过username查找用户信息 **/
public UserInfo findByUsername(String username);
}
package com.example.service;
import com.example.domain.UserInfo;
public interface UserInfoService {
public UserInfo findByUsername(String username);
}
package com.example.service.impl;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.example.domain.UserInfo;
import com.example.repository.UserInfoRepository;
import com.example.service.UserInfoService;
@Service
public class UserInfoServiceImpl implements UserInfoService{
@Resource
private UserInfoRepository userInfoRepository;
@Transactional(readOnly=true)
@Override
public UserInfo findByUsername(String username) {
System.out.println("UserInfoServiceImpl.findByUsername()");
return userInfoRepository.findByUsername(username);
}
}
package com.example.config.shiro;
import javax.annotation.Resource;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.context.annotation.Bean;
import com.example.domain.SysPermission;
import com.example.domain.SysRole;
import com.example.domain.UserInfo;
import com.example.service.UserInfoService;
/**
* 身份校验核心类
*
* @author Administrator
*
*/
public class MyShiroRealm extends AuthorizingRealm {
@Resource
private UserInfoService userInfoService;
/**
* 认证信息(身份验证) Authentication 是用来验证用户身份
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
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;
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(userInfo, // 用户名
userInfo.getPassword(), // 密码
ByteSource.Util.bytes(userInfo.getCredentialsSalt()), // salt=username+salt
getName() // realm name
);
return authenticationInfo;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// TODO Auto-generated method stub
System.out.println("权限配置-->MyShiroRealm.doGetAuthorizationInfo()");
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
UserInfo userInfo = (UserInfo) principals.getPrimaryPrincipal();
for(SysRole role:userInfo.getRoleList()){
authorizationInfo.addRole(role.getRole());
System.out.println(role.getPermissions());
for(SysPermission p:role.getPermissions()){
System.out.println(p);
authorizationInfo.addStringPermission(p.getPermission());
}
}
return authorizationInfo;
}
}
SimpleAuthenticationInfoauthenticationInfo =
new SimpleAuthenticationInfo(
userInfo, //用户名
userInfo.getPassword(), //密码
ByteSource.Util.bytes(userInfo.getCredentialsSalt()),//salt=username+salt
getName() //realm name
);
交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配,如果觉得人家的不好可以自定义实现
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
userInfo, //用户名
userInfo.getPassword(), //密码
getName() //realm name
);
至于doGetAuthorizationInfo()是权限控制,当访问到页面的时候,使用了相应的注解或者shiro标签才会执行此方法否则不会执行,所以如果只是简单的身份认证没有权限的控制的话,那么这个方法可以不进行实现,直接返回null即可。
到这里我们还需要有一个步骤很重要就是将我们自定义的Realm注入到SecurityManager中。
在ShiroConfiguration.java中添加方法
/**
* 身份认证realm;
*
*/
@Bean
public MyShiroRealm myShiroRealm(){
MyShiroRealm myShiroRealm = new MyShiroRealm();
return myShiroRealm;
}
将myShiroRealm注入到securityManager中:
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//设置realm.
securityManager.setRealm(myShiroRealm());
return securityManager;
}
到这里的话身份认证权限控制基本是完成了,最后我们在编写一个登录的时候,登录的处理:
package com.example.controller;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class HomeController {
@RequestMapping({ "/", "index" })
public String index() {
return "/index";
}
@RequestMapping(value = "/login", method = RequestMethod.GET)
public String login() {
return "/login";
}
@RequestMapping(value = "/login", method = RequestMethod.POST)
public String login(HttpServletRequest request, Map map) {
System.out.println("HomeController.login");
// 登录失败从request中获取shiro处理的异常信息
// shiroLoginFailure:就是shiro异常类的全类名
String exception = (String) request.getAttribute("shiroLoginFailure");
String msg = "";
if (exception != null) {
if (UnknownAccountException.class.getName().equals(exception)) {
System.out.println("UnknownAccountException -->帐号不存在:");
msg = "UnknownAccountException -->帐号不存在:";
} else if (IncorrectCredentialsException.class.getName().equals(exception)) {
System.out.println("IncorrectCredentialsException -- > 密码不正确:");
msg = "IncorrectCredentialsException -- > 密码不正确:";
} else if ("kaptchaValidateFailed".equals(exception)) {
System.out.println("kaptchaValidateFailed -- > 验证码错误");
msg = "kaptchaValidateFailed -- > 验证码错误";
} else {
msg = "else >> " + exception;
System.out.println("else -- >" + exception);
}
}
map.put("msg", msg);
// 此方法不处理登录成功,由shiro进行处理.
return "/login";
}
}
这时候我们启动应用程序,访问http://127.0.0.1:8080/index
在这里我们需要编写一个加密算法类,当然Shiro也已经有了具体的实现HashedCredentialsMatcher
我们只需要进行注入使用即可:
在ShiroConfiguration中加入方法:
/**
* 凭证匹配器
* (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
* 所以我们需要修改下doGetAuthenticationInfo中的代码;
* )
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5(""));
return hashedCredentialsMatcher;
}
在myShiroRealm()方法中注入凭证匹配器:
@Bean
public MyShiroRealm myShiroRealm(){
MyShiroRealm myShiroRealm = new MyShiroRealm();
myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());;
return myShiroRealm;
}
这时候在访问/login进行登录就可以登陆到/index界面了。
package com.example.controller;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("userInfo")
public class UserInfoController {
/**
* 用户查询.
* @return
*/
@RequestMapping("/userList")
public String userInfo(){
return "userInfo";
}
/**
* 用户添加;
* @return
*/
@RequestMapping("/userAdd")
public String userInfoAdd(){
return "userInfoAdd";
}
/**
* 用户删除;
* @return
*/
@RequestMapping("/userDel")
@RequiresPermissions("userInfo:del")//权限管理;
public String userDel(){
return "userInfoDel";
}
}
我们少了几部分代码,
第一就是开启shiro aop注解支持,这个只需要在ShiroConfiguration加入如下方法进行开启即可:
/**
* 开启shiro aop注解支持.
* 使用代理方式;所以需要开启代码支持;
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
第二就是在controller方法中加入相应的注解:
package com.example.controller;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("userInfo")
public class UserInfoController {
/**
* 用户查询.
* @return
*/
@RequestMapping("/userList")
@RequiresPermissions("userInfo:view")//权限管理;
public String userInfo(){
return "userInfo";
}
/**
* 用户添加;
* @return
*/
@RequestMapping("/userAdd")
@RequiresPermissions("userInfo:add")//权限管理;
public String userInfoAdd(){
return "userInfoAdd";
}
/**
* 用户删除;
* @return
*/
@RequestMapping("/userDel")
@RequiresPermissions("userInfo:del")//权限管理;
public String userDel(){
return "userInfoDel";
}
}
这时候在访问http://127.0.0.1:8080/userInfo/userAdd 会看到控制台打印信息
权限配置-->MyShiroRealm.doGetAuthorizationInfo()
如果访问:http://127.0.0.1:8080/userInfo/userDel会看到控制台打印信息
org.apache.shiro.authz.AuthorizationException: Not authorized to invoke method
页面上看到
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Sun Aug 28 21:36:31 CST 2016
There was an unexpected error (type=Internal Server Error, status=500).
Subject does not have permission [userInfo:del]
----------------------------------------------
pom.xml
4.0.0
com.example
spring-boot-shiro1
0.0.1-SNAPSHOT
jar
UTF-8
org.springframework.boot
spring-boot-starter-parent
1.4.0.RELEASE
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-thymeleaf
org.apache.shiro
shiro-spring
1.2.3
org.springframework.boot
spring-boot-starter-data-jpa
org.springframework.boot
spring-boot-devtools
true
mysql
mysql-connector-java
ShiroConfiguration.java
package com.example.config.shiro;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ShiroConfiguration {
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
System.out.println("ShiroConfiguration.shiroFilter()");
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必须设置SecuritManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 拦截器
Map filterChainDefinitionMap = new LinkedHashMap();
// 配置退出过滤器,其中的具体代码Shiro已经替我们实现了
filterChainDefinitionMap.put("/logout", "logout");
// :这是一个坑呢,一不小心代码就不好使了;
//
filterChainDefinitionMap.put("/**", "authc");
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/login");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/index");
// 未授权界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置realm.
securityManager.setRealm(myShiroRealm());
return securityManager;
}
/**
* 身份认证realm; (这个需要自己写,账号密码校验;权限等)
*
* @return
*/
@Bean
public MyShiroRealm myShiroRealm() {
MyShiroRealm myShiroRealm = new MyShiroRealm();
myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return myShiroRealm;
}
/**
* 凭证匹配器
* (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
* 所以我们需要修改下doGetAuthenticationInfo中的代码;
* )
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5(""));
return hashedCredentialsMatcher;
}
/**
* 开启shiro aop注解支持.
* 使用代理方式;所以需要开启代码支持;
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}