1.如果是默認使用username password
常規配置文件pom依賴
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.jsdc</groupId>
<artifactId>tianqi</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>tianqi</name>
<description>this is my project</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- Spring Boot Web 依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<!-- spring boot 默認的日誌框架是Logback,所以在引用log4j之前,需要先排除該包的依賴,再引入log4j2的依賴 -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<!-- Spring Boot Test 依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!-- shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>com.jagregory</groupId>
<artifactId>shiro-freemarker-tags</artifactId>
<version>0.2</version>
<exclusions>
<exclusion>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
</exclusion>
<exclusion>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
</dependency>
<!-- Spring Boot Mybatis 依賴 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
<!--MySql 驅動-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.6</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.29</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.7</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
yaml文件
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/tianqi?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
freemarker:
request-context-attribute: req
suffix: .ftl
content-type: text/html
enabled: true
template-loader-path: classpath:/templates
charset: utf-8
logging:
level:
com.jsdc.tianqi: debug
shiro配置文件
package com.jsdc.tianqi.config;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
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(name = "shiroDbRealm")
public SystemAuthorizingRealm getShiroDbRealm() {
return new SystemAuthorizingRealm();
}
@Bean(name = "rememberMeCookie")
public SimpleCookie getRememberMeCookie() {
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
simpleCookie.setHttpOnly(true);
simpleCookie.setMaxAge(-1);
return simpleCookie;
}
/**
* 這裏指定的是通過加鹽的方式來驗證密碼,注意下面的配置
* @param systemAuthorizingRealm
* @return
*/
@Bean(name = "credentialsMatcher")
public CredentialsMatcher getHashedCredentialsMatcher(SystemAuthorizingRealm systemAuthorizingRealm){
//1
// .參數SystemAuthorizingRealm必須有
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
credentialsMatcher.setHashAlgorithmName("MD5");//2.指定散列方式
credentialsMatcher.setHashIterations(1);//3.散列次數
systemAuthorizingRealm.setCredentialsMatcher(credentialsMatcher);//4.很關鍵的一步,要把驗證方式注入到自定義realm裏面去
return credentialsMatcher;
}
@Bean(name = "rememberMeManager")
public CookieRememberMeManager getRememberMeManager(SimpleCookie cookie) {
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCipherKey(Base64.decode("4AvVhmFLUs0KTA3Kprsdag=="));
cookieRememberMeManager.setCookie(cookie);
return cookieRememberMeManager;
}
@Bean(name = "securityManager")
public DefaultWebSecurityManager getSecurityManager(SystemAuthorizingRealm realm, CookieRememberMeManager rememberMeManager) {
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRememberMeManager(rememberMeManager);
defaultWebSecurityManager.setRealm(realm);
return defaultWebSecurityManager;
}
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean getShiroFilter(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
shiroFilterFactoryBean.setLoginUrl("/login.do");
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/login.do", "authc");
filterChainDefinitionMap.put("/logout.do", "logout");
filterChainDefinitionMap.put("/libs/**", "anon");
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean(name = "lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
@Bean
public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
自定義realm
package com.jsdc.tianqi.config;
import com.jsdc.tianqi.po.User;
import com.jsdc.tianqi.utils.PasswordUtils;
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.apache.shiro.util.ByteSource;
import org.apache.shiro.util.SimpleByteSource;
public class SystemAuthorizingRealm extends AuthorizingRealm {
/**
* 認證回調函數,登錄時調用.
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
//這裏有幾種方式:
//1.update token裏的password,將password加密
//2.明文不需要操作
//3.指定CredentialsMatcher 進行密碼比對
//以下內容來自數據源查詢而來
User user = new User();
user.setId(1);
user.setUsername("admin");
user.setPassword("a6d4f0a9c109cd24eacb88e75e5be690");
//這裏構造方法有幾個,比如需要配置salt,就用下面的構造方法
// SimpleAuthenticationInfo(Object principal, Object hashedCredentials, ByteSource credentialsSalt, String realmName)
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user.getUsername(),
user.getPassword(), ByteSource.Util.bytes(PasswordUtils.SALT), getName());
return info;
}
/**
* 授權查詢回調函數, 進行鑑權但緩存中無用戶的授權信息時調用
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
}
控制器代碼
/**
* 登錄頁面
*/
@RequestMapping(value = "login.do", method = RequestMethod.GET)
public String login(){
return "login";
}
@RequestMapping(value = "login.do", method = RequestMethod.POST)
public String login(@RequestAttribute(required = false)String shiroLoginFailure) throws Exception {
if (shiroLoginFailure != null) {
if (UnknownAccountException.class.getName().equals(shiroLoginFailure)) {
System.out.println("用戶名不存在!請確認用戶名和密碼後重新登錄");
} else if (IncorrectCredentialsException.class.getName().equals(shiroLoginFailure)) {
System.out.println("用戶名或密碼錯誤!請確認用戶名和密碼後重新登錄");
} else {
System.out.println("登錄失敗!請確認用戶名和密碼後重新登錄");
}
}
return "login";
}
這裏解釋一下:
shiro認證的流程,首先在配置文件指定loginurl,並對loginurL配置 FormAuthenticationFilter,簡稱authc
即過濾鏈中的filterChainDefinitionMap.put("/login.do", "authc");
然後,控制器登錄頁提交地址必須跟配置文件保持一致,當訪問login.do的時候,直接被filter攔截,並同realm做認證,如果認證成功了,會默認返回上一個頁面。(這裏也可以配置文件設置successUrl,包括失敗頁面,但是沒有必要)
就完成了登錄驗證。
也就說,登錄成功跳轉哪裏,交給shiro,login.do只需要判斷有沒有返回異常shiroLoginFailure,有進行提示,這個方法裏不做任何成功的跳轉。
2.如果是自定義userName,passWord
控制器代碼
@RequestMapping(value = "login.do", method = RequestMethod.POST)
public String login(String userName, String passWord, Boolean rememberMe) throws Exception {
if(tqzlService.login(userName,passWord)){
return "redirect:main";
}
return "redirect:login.do";
}
service代碼
public boolean login(String userName,String passWord){
if(userName.equals("aa")){//這裏去數據庫查詢,如果user不存在 返回false.不然下面代碼會報錯500
return false;
}
Subject subject = SecurityUtils.getSubject();
// 利用Host屬性存放其他屬性,用逗號分隔保存,第二位是userId
String host = "0.0.0.0," + 1;
UsernamePasswordToken token = new UsernamePasswordToken(userName, passWord, true, host);
subject.login(token);
return true;
}
認證回調也要判斷User是否存在,不存在直接return null 否則報錯
/**
* 認證回調函數,登錄時調用.
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
//這裏有幾種方式:
//1.update token裏的password,將password加密
//2.明文不需要操作
//3.指定CredentialsMatcher 進行密碼比對
if(token.getUsername().equals("aa")){//這裏去數據庫查詢,如果user不存在 返回false.不然下面代碼會報錯500
return null;
}
//以下內容來自數據源查詢而來
User user = new User();
user.setId(1);
user.setUsername("admin");
user.setPassword("1234");
//這裏構造方法有幾個,比如需要配置salt,就用下面的構造方法
// SimpleAuthenticationInfo(Object principal, Object hashedCredentials, ByteSource credentialsSalt, String realmName)
SimpleAuthenticationInfo info= new SimpleAuthenticationInfo(new ShiroUser(user.getId().toString(), user.getUsername()),
user.getPassword(), getName());
return info;
}