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实现。
以上内容摘自百度百科
shiro安全控制分为身份认证和权限认证
1、用户身份认证
认证信息包括两部分:身份(Principal)和凭证(Credential)。
身份:通常可以是用户账号、邮箱、手机号等。
凭证:通常为用户密码、证书等
用户在提交一个认证操作时将用户身份和凭证交给一个认证主体,即Subject,然后Subject将认证操作交给securitymanager,由它调用内部组件完成具体认证操作
2、用户权限认证
用户:访问系统的用户为一个shiro用户,一个用户可以有多个角色,一个角色可以有多个权限。
角色和权限:访问或操作资源的资格,shiro控制某个资源可以被担任某个角色或拥有某个权限的用户访问和操作。
以下为代码整理部分
一、创建一个springboot工程、加入shiro依赖、web依赖、hibernate依赖、MySQL依赖
4.0.0
org.springframework.boot
spring-boot-starter-parent
1.5.21.RELEASE
com.jx
shirodemo
0.0.1-SNAPSHOT
shirodemo
Demo project for Spring Boot
1.8
org.springframework.boot
spring-boot-starter-data-jpa
org.springframework.boot
spring-boot-starter-web
mysql
mysql-connector-java
org.apache.shiro
shiro-spring
1.4.0
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-maven-plugin
二、配置hibernate和MySQL相关配置
server.port=80
spring.datasource.url=jdbc:mysql://localhost:3306/securitydemo?useunicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.max-idle=10
spring.datasource.max-wait=10000
spring.datasource.min-idle=5
spring.datasource.initial-size=5
spring.jpa.database = MYSQL
spring.jpa.show-sql = true
spring.jpa.hibernate.ddl-auto = update
spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.ImprovedNamingStrategy
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
spring.jpa.properties.hibernate.current_session_context_class=org.springframework.orm.hibernate4.SpringSessionContext
三、编写具体实现过程代码
1、编写用户类
package com.jx.shirodemo.bean;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import javax.persistence.*;
import java.io.Serializable;
/*
* 用户封装类
* */
@Component
@Scope(value = "prototype")
@Entity
@Table(name="t_user")
public class User implements Serializable {
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
private Integer id;
@Column(name="user_name")
private String userName;//用户名
@Column(name = "password")
private String password;//用户密码
@Column(name="roles")
private String roles;//用户角色
@Column(name = "perms")
private String permissions;//用户权限
public String getRoles() {
return roles;
}
public void setRoles(String roles) {
this.roles = roles;
}
public String getPermissions() {
return permissions;
}
public void setPermissions(String permissions) {
this.permissions = permissions;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
2、编写状态码封装类
package com.jx.shirodemo.bean;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
/*
* 状态码封装类
* */
@Component
@Scope(value = "prototype")
public class State {
private String state;
private String msg;
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
3、编写hibernate工具类和hibernate配置类
package com.jx.shirodemo.hibernate;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/*
* hibernate工具类
* */
@Component
public class HibernateUtils {
@Autowired
private SessionFactory sessionFactory ;
/*
* 开启一个session
* */
public Session openSession(){
return sessionFactory.openSession();
}
/*
* 关闭session
* */
public void closeSession(Session session){
if(session!=null&&session.isOpen()) {
session.close();
}
}
}
package com.jx.shirodemo.hibernate;
import org.hibernate.SessionFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.persistence.EntityManagerFactory;
@Configuration
public class HibernateConfig {
/*
* 向spring容器添加一个sessionfactory对象
* */
@Bean
SessionFactory sessionFactory(EntityManagerFactory entityManagerFactory){
return entityManagerFactory.unwrap(SessionFactory.class);
}
}
4、编写数据操作类
package com.jx.shirodemo.dao;
import com.jx.shirodemo.bean.User;
import com.jx.shirodemo.hibernate.HibernateUtils;
import org.hibernate.Session;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.util.List;
/*
* 数据库操作类
* */
@Repository
public class UserDao {
@Autowired
private HibernateUtils hibernateUtils;
/*
* 根据用户名查找用户信息,返回一个用户
* */
public User findUserByUsername(String userName){
Session session = null;
List list = null;
String sql = "select * from t_user where user_name = '"+userName+"'";
try{
//开启一个session,执行数据操作SQL
session = hibernateUtils.openSession();
list = session.createSQLQuery(sql).addEntity(User.class).list();
}catch (Exception e){
throw e;
}finally {
//关闭session
hibernateUtils.closeSession(session);
}
//判断返回集合是否为空,非空封装信息返回
if(list==null|list.isEmpty()){
return null ;
}else{
User user = list.get(0);
return user;
}
}
/*
* 添加一个用户
* */
public void addUser(User user){
Session session = null;
try{
//开启一个session操作数据库
session = hibernateUtils.openSession();
session.save(user);
}catch (Exception e){
throw e;
}finally {
//关闭session
hibernateUtils.closeSession(session);
}
}
}
5、编写服务类
package com.jx.shirodemo.service;
import com.jx.shirodemo.bean.State;
import com.jx.shirodemo.bean.User;
import com.jx.shirodemo.dao.UserDao;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/*
* 用户服务类
* */
@Service
public class UserSerivce {
@Autowired
private UserDao userDao;
/*
* 通过用户名查询用户信息
* */
public User getUserByname(String userName){
return userDao.findUserByUsername(userName);
}
/*
* 根据用户名密码登录
* */
public State login(String username,String password){
State state = null;
//判断用户账号或密码是否为空
if(username==null|"".equals(username)|password==null|"".equals(password)){
state = new State();
state.setState("0");
state.setMsg("账号或密码不能为空");
return state;
}
//根据用户名密码创建token
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username,password);
//获取认证主体subject
Subject subject = SecurityUtils.getSubject();
try{
//根据用户信息登录
subject.login(usernamePasswordToken);
}catch (Exception e){
e.printStackTrace();
state = new State();
state.setState("0");
state.setMsg("登录失败");
return state;
}
state = new State();
state.setState("1");
state.setMsg("登录成功");
return state;
}
/*
* 新用户注册
* */
public State register(String username, String password, String roles, String perms){
State state = null;
//判断用户账号、密码、角色、权限是否为空
if(username==null|"".equals(username)|password==null|"".equals(password)|roles==null|"".equals(roles)|perms==null|"".equals(perms)){
state = new State();
state.setState("0");
state.setMsg("账号、密码、角色、权限任一一项不可为空");
return state;
}
//根据用户名查询用户判断是否存在该用户
User user = userDao.findUserByUsername(username);
if(user==null){
user = new User();
user.setUserName(username);
//设置用户名为盐值
String salt = username;
//用户密码加密
SimpleHash encryptionPassword = new SimpleHash("MD5",
password, ByteSource.Util.bytes(salt),
1);
System.out.println(encryptionPassword);
user.setPassword(encryptionPassword.toString());
user.setRoles(roles);
user.setPermissions(perms);
try {
userDao.addUser(user);//添加用户
}catch (Exception e){
state = new State();
state.setState("0");
state.setMsg("注册失败");
return state;
}
state = new State();
state.setState("1");
state.setMsg("注册成功");
return state;
}else {
state = new State();
state.setState("0");
state.setMsg("用户已存在");
return state;
}
}
}
6、自定义realm类,继承AuthorizingRealm类
package com.jx.shirodemo.shiro;
import com.jx.shirodemo.bean.User;
import com.jx.shirodemo.service.UserSerivce;
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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.HashSet;
import java.util.Set;
/*
* 自定义realm
* */
public class MyRealm extends AuthorizingRealm {
@Autowired
private UserSerivce userSerivce;
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//通过authenticationToken获取用户名
String username = (String)authenticationToken.getPrincipal();
//根据用户名从数据库查询用户信息
User user = userSerivce.getUserByname(username);
//判断用户是否存在
if(user!=null){
//认证用户信息
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user.getUserName(),user.getPassword(),"myRealm");
//设置盐值加密
authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(username));
return authenticationInfo;
}else{
return null ;
}
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取用户名
String userNmae = (String)principalCollection.getPrimaryPrincipal();
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//根据用户名获取用户角色和权限并授权
//只创建了一张用户表(包含账号、密码、角色、权限),这里手动创建Set集合存放角色和权限
Set roles = new HashSet();
roles.add(userSerivce.getUserByname(userNmae).getRoles());
Set permissions = new HashSet();
permissions.add(userSerivce.getUserByname(userNmae).getPermissions());
//给用户授权
simpleAuthorizationInfo.setRoles(roles);
simpleAuthorizationInfo.setStringPermissions(permissions);
return simpleAuthorizationInfo;
}
}
7、编写shiro配置类
package com.jx.shirodemo.shiro;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
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;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
/*
* 配置密码匹配器
* */
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("MD5");
hashedCredentialsMatcher.setHashIterations(1);
hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
return hashedCredentialsMatcher;
}
/*
* 配置自定义realm,设置密码匹配器
* */
@Bean
public MyRealm myRealm(HashedCredentialsMatcher hashedCredentialsMatcher){
MyRealm myRealm = new MyRealm();
myRealm.setCredentialsMatcher(hashedCredentialsMatcher);
return myRealm;
}
/*
* 配置安全管理器,传入自定义realm
* */
@Bean
public SecurityManager securityManager(MyRealm myRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(myRealm);
return securityManager;
}
/*
* 配置shiro拦截器
* */
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//身份认证失败跳转到登录页面
shiroFilterFactoryBean.setLoginUrl("/login");
//身份认证成功跳转
// shiroFilterFactoryBean.setSuccessUrl("/ok");
//未授权页面
shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");
//创建拦截链集合,LinkedHashMap有序集合
Map filterMap = new LinkedHashMap();
//所有静态资源无需身份认证
//anon无需认证
filterMap.put("/register/**","anon");//URL以/userRegister/开始的都无需认证
filterMap.put("/login/**","anon");
//authc需要身份认证
filterMap.put("/user","authc");
//roles[admin]需要角色认证,admin角色才可访问
filterMap.put("/admin","roles[admin]");
//perms["change"]需要权限认证,change权限才可以访问
filterMap.put("/change","perms[change]");
//退出跳转
filterMap.put("/logout","authc");
//其他页面都需要身份认证
filterMap.put("/**","authc");
//注入拦截链到拦截器
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
//注入安全管理器到拦截器
shiroFilterFactoryBean.setSecurityManager(securityManager);
return shiroFilterFactoryBean;
}
}
8、编写对外访问接口类
package com.jx.shirodemo.controller;
import com.jx.shirodemo.bean.State;
import com.jx.shirodemo.service.UserSerivce;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
/*
* 对外访问接口类
* */
@RestController
public class UserController {
@Autowired
private UserSerivce userSerivce;
private State state ;
@Autowired
private BeanFactory beanFactory;
//用户
@RequestMapping("/user")
public State user(){
state = beanFactory.getBean(State.class);
state.setState("1");
state.setMsg("该用户有访问user的权限");
return state;
}
//未认证
@RequestMapping("/unauthorized")
public State unauthorized(){
state = beanFactory.getBean(State.class);
state.setState("0");
state.setMsg("该用户未认证或未授权访问");
return state;
}
//管理
@RequestMapping("/admin")
public State admin(){
state = beanFactory.getBean(State.class);
state.setState("1");
state.setMsg("该用户有访问admin的权限");
return state;
}
//修改
@RequestMapping("/change")
public State change(){
state = beanFactory.getBean(State.class);
state.setState("1");
state.setMsg("该用户有访问change的权限");
return state;
}
//退出
@RequestMapping("/logout")
public State logout(){
state = beanFactory.getBean(State.class);
state.setState("1");
state.setMsg("该用户有访问logout的权限");
return state;
}
//注册用户接口
@RequestMapping("/register/{username}/{password}/{roles}/{perms}")
public State register(@PathVariable("username") String uername, @PathVariable("password") String password,
@PathVariable("roles")String roles, @PathVariable("perms")String perms){
return userSerivce.register(uername,password,roles,perms);
}
//用户登录接口
@RequestMapping("/login/{username}/{password}")
public State userLogin(@PathVariable("username")String username,@PathVariable("password")String password ){
return userSerivce.login(username,password);
}
}
四、运行测试
1、注册2个用户如下
2、分别访问http://localhost/admin、http://localhost/user、http://localhost/change都会转到login页面,这里因为为方便测试/login接口带参数,后台设置为不带参数,所以404了
3、使用zhangsan用户登录、并再次访问上述接口
3.1、登录
3.2、
4、再次使用zhangsan01用户登录
4.1、登录
4.2再次访问上述接口,http://localhost/admin和http://localhost/change均因权限问题无法访问
http://localhost/user只需用户身份认证(即登录)即可访问
2个用户的权限可通过比对数据库和shiro过滤器配置权限