springboot集成shiro实现身份认证
github地址:https://github.com/peterowang/shiro
pom文件
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- mysql驱动; -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>net.sourceforge.nekohtml</groupId>
<artifactId>nekohtml</artifactId>
<version>1.9.22</version>
</dependency>
<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.2.2</version>
</dependency>
<!-- Spirng data JPA依赖; -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
1.集成spring data jpa
2.使用thymeleaf 见application.properties
spring.application.name=spring-boot-shiro
server.port=8080
spring.thymeleaf.mode=LEGACYHTML5 这里的设置主要是因为thymeleaf校验html文件的时候会特别严格,比如<input> 必须加上/
结尾,这里需要依赖nekohtml
.
spring.thymeleaf.cache=false
spring.thymeleaf.prefix=classpath:/templates/ 这里相当于springmvc里的视图解析器
spring.thymeleaf.suffix=.html
########################################################
###datasource
########################################################
spring.datasource.url = jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf8
spring.datasource.username = root
spring.datasource.password = root
spring.datasource.driverClassName = com.mysql.jdbc.Driver
spring.datasource.max-active=20
spring.datasource.min-active=10
spring.datasource.max-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
目录结构 :
3.配置realm
Realm是一个Dao,通过它来验证用户身份和权限。这里Shiro不做权限的管理工作,需要我们自己管理用户权限,只需要从我们的数据源中把用户和用户的角色权限信息取出来交给Shiro即可。
在
在
config
包下再建一个包Shiro
,然后在Shiro包下建一个MyShiroRealm
类,继承AuthorizingRealm抽象类。package com.example.demo.config.shiro;
import com.example.demo.model.UserInfo;
import com.example.demo.service.UserInfoService;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Created by BFD-593 on 2017/8/8.
*/
public class MyShiroRealm extends AuthorizingRealm{
private static final Logger logger = LoggerFactory.getLogger(MyShiroRealm.class);
@Autowired
private UserInfoService userInfoService;
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
logger.info("开始身份验证");
String username = (String) token.getPrincipal();
UserInfo userInfo = userInfoService.findByUsername(username);
if(userInfo==null) {
return null;
}
SimpleAuthenticationInfo auth = new SimpleAuthenticationInfo(
userInfo,
userInfo.getPassword(), //密码
getName() //realm name
);
return auth;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
}
4.配置自定义密码比较器(shiro会根据这个来将用户输入的密码加密成注册时的密码,与db里的密码比较)
package com.example.demo.config.shiro;5.shiroConfig配置类
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;
import org.apache.shiro.crypto.hash.Md5Hash;
/**
* Created by BFD-593 on 2017/8/8.
*/
public class CredentialsMatcher extends SimpleCredentialsMatcher {
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
UsernamePasswordToken utoken=(UsernamePasswordToken) token;
//获得用户输入的密码:(可以采用加盐(salt)的方式去检验)
String password = String.valueOf(utoken.getPassword());
String username = String.valueOf(utoken.getUsername());
Md5Hash md5 = new Md5Hash(password,username, 2);
String inPassword = md5.toString();
//获得数据库中的密码
String dbPassword=(String) info.getCredentials();
//进行密码的比对
return this.equals(inPassword, dbPassword);
}
}
这里要配置的是ShiroConfig类,Apache Shiro 核心通过 Filter 来实现,就好像SpringMvc 通过DispachServlet 来主控制一样。 既然是使用 Filter 一般也就能猜到,是通过URL规则来进行过滤和权限校验,所以我们需要定义一系列关于URL的规则和访问权限。
package com.example.demo.config.shiro;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Created by BFD-593 on 2017/8/8.
*/
@Configuration
public class ShiroConfiguration {
@Bean(name="shiroFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") SecurityManager manager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(manager);
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/logout", "logout");
filterChainDefinitionMap.put("/favicon.ico", "anon");
filterChainDefinitionMap.put("/**", "authc");
//authc表示需要验证身份才能访问,还有一些比如anon表示不需要验证身份就能访问等。
//关于为什么设置filterChainDefinitionMap.put("/favicon.ico", "anon");,请参考Shiro登录后下载favicon.ico问题
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setSuccessUrl("/index");
// shiroFilterFactoryBean.setUnauthorizedUrl("/403"); //这里设置403并不会起作用,参考http://www.jianshu.com/p/e03f5b54838c
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* 不指定名字的话,自动创建一个方法名第一个字母小写的bean
* @Bean(name = "securityManager")
* @return
*/
@Bean(name="securityManager")
public SecurityManager securityManager(@Qualifier("authRealm") MyShiroRealm authRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(authRealm);
return securityManager;
}
/**
* Shiro Realm 继承自AuthorizingRealm的自定义Realm,即指定Shiro验证用户登录的类为自定义的
*
* @param
* @return
*/
@Bean(name="authRealm")
public MyShiroRealm userRealm(@Qualifier("credentialsMatcher") CredentialsMatcher matcher) {
MyShiroRealm userRealm = new MyShiroRealm();
//告诉realm,使用credentialsMatcher加密算法类来验证密文
userRealm.setCredentialsMatcher(matcher);
return userRealm;
}
/**
* 密码比较器
* @return
*/
@Bean(name="credentialsMatcher")
public CredentialsMatcher credentialsMatcher() {
return new CredentialsMatcher();
}
}
6.web层
package com.example.demo.web;
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 javax.servlet.http.HttpServletRequest;
import java.util.Map;
/**
* Created by BFD-593 on 2017/8/8.
*/
@Controller
public class HomeController {
@RequestMapping({"/","/index"})
public String index() {
return "index";
}
/**
* logout由shiro实现,我们只需提供入口即可
* filterChainDefinitionMap.put("/logout", "logout");
* @return
*/
@RequestMapping({"/logout"})
public String logout(){
return "login";
}
/**
* 因为设置了setLoginUrl("/login");登录url如果没有登录,
* 所有的请求都发送到这里。shiro会自动调用securityManager
* 此方法不处理登录成功,由shiro进行处理。
* @param request
* @param map
* @return
* @throws Exception
*/
@RequestMapping("/login")
public String login(HttpServletRequest request, Map<String, Object> map) throws Exception {
// 登录失败从request中获取shiro处理的异常信息。
// shiroLoginFailure:就是shiro异常类的全类名.
String exceptionClassName = (String)request.getAttribute("shiroLoginFailure");
String error = null;
if(UnknownAccountException.class.getName().equals(exceptionClassName)) {
error = "用户名/密码错误";
} else if(IncorrectCredentialsException.class.getName().equals(exceptionClassName)) {
error = "用户名/密码错误";
} else if(exceptionClassName != null) {
error = "其他错误:" + exceptionClassName;
}
map.put("msg", error);
return "login";
}
}
7.UserInfo:
package com.example.demo.model;
import javax.persistence.*;
import java.io.Serializable;
/**
* Created by archerlj on 2017/6/30.
*/
@Entity
@Table(name="user_info")
public class UserInfo implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;//用户id;
@Column(unique = true, name = "username")
private String username;//账号.
@Column(name = "password")
private String password; //密码;
@Column(name = "salt")
private String salt;//加密密码的盐
@Column(name = "state")
private byte state;//用户状态,0:创建未认证(比如没有激活,没有输入验证码等等)--等待验证的用户 , 1:正常状态,2:用户被锁定.
public long getId() {
return id;
}
public void setId(long 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;
}
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;
}
}
8.UserInfoRespository
package com.example.demo.dao;
import com.example.demo.model.UserInfo;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* Created by BFD-593 on 2017/8/8.
*/
public interface UserInfoRepository extends JpaRepository<UserInfo,Long> {
public UserInfo findByUsername(String username);
public UserInfo save(UserInfo userInfo);
}
9.UserInfoService:
import com.example.demo.dao.UserInfoRepository;
import com.example.demo.model.UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* Created by BFD-593 on 2017/8/8.
*/
@Service
public class UserInfoService {
@Autowired
private UserInfoRepository userInfoRepository;
public UserInfo findByUsername(String username){
return userInfoRepository.findByUsername(username);
}
}
10.配置jpa请看之前博客