一、准备
1、pom
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--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-web</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.3.2</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.2</version>
</dependency>
<!--导入html依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
2、application.yml
spring:
datasource:
url: jdbc:mysql://ip:port/shiro3?characterEncoding=utf-8&useSSL=true
driver-class-name: com.mysql.jdbc.Driver
username: xxx
password: xxx
--自动创建表格
jpa:
show-sql: true
hibernate:
ddl-auto: update
#thymeleaf模版前缀:也可以不设置,以下是默认的
thymeleaf:
prefix: classpath:/templates/
cache: true
mode: HTML5
encoding: UTF-8
servlet:
content-type: text/html
redis:
database: 10
host: http://127.0.0.0
port: 6379
timeout: 0
aop:
proxy-target-class: true
server:
port: 8081
二、登录认证
1、登录页面
PageController
@Controller
@RequestMapping("/page")
public class PageController {
/**
* 这里不要加@ResponseBody,也就是不要以json方式返回,
* 因为你要重定向到/templates/下的login.html页面
* @return
*/
@RequestMapping("/login")
public String login(){
return "login";
}
html页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/user/login" method="post">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
记住我:<input type="checkbox" name="rememberMe"><br>
<input type="submit" value="登录">
</form>
</body>
</html>
启动项目,请求接口,如下图
UserController
@Controller
@RequestMapping("/usr")
public class UserController {
@PostMapping("/login")
@ResponseBody
public String login(User user){
Subject subject = SecurityUtils.getSubject();
//用来接收来自前端的信息,比如username,password,host,rememeberme等
UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(),user.getPassword());
try {
subject.login(token);
} catch (AuthenticationException e) {
e.printStackTrace();
return e.getMessage();
}
return "success login";
}
}
ShiroConfig
@Configuration
public class ShiroConfig {
//过滤器
@Bean
public ShiroFilterFactoryBean filterFactoryBean(@Qualifier("securityManager") SecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager); // 必须设置 SecurityManager
// 拦截器.
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
// 配置不会被拦截的链接 顺序判断
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/images/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
//不会拦截的url
filterChainDefinitionMap.put("/page/login","anon"); --login.html
filterChainDefinitionMap.put("/usr/login","anon"); --登录url
//所有url都必须有user权限才可以访问 一般讲放在最后面
filterChainDefinitionMap.put("/**", "user");
//重定向链接
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorizied");
shiroFilterFactoryBean.setSuccessUrl("/");
//设置拦截器
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean(name = "securityManager")
public SecurityManager securityManager(@Qualifier("customRealm")CustomRealm customRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(customRealm);
return securityManager;
}
@Bean(name = "customRealm")
public CustomRealm customRealm(){
CustomRealm customRealm = new CustomRealm();
return customRealm;
}
}
CustomRealm
public class CustomRealm extends AuthorizingRealm{
@Autowired
private UserService userService;
@Autowired
private RoleService roleService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
User user = (User)principal.getPrimaryPrincipal();
Long idd = Long.valueOf(user.getId());
Set<String> roles = roleService.getRolesById(idd);
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.setRoles(roles);
return simpleAuthorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
User user = userService.getUserByUsername(token.getUsername());
SimpleAuthenticationInfo simpleAuthenticationInfo = null;
if (user != null) {
try {
simpleAuthenticationInfo = new SimpleAuthenticationInfo(user, user.getPassword(), getName());
} catch (Exception e) {
e.printStackTrace();
}
return simpleAuthenticationInfo;
} else {
return null;
}
}
}
domain,service,repository见我的github
2、加密
ShiroConfig
@Bean //配置HashedCredentialsMatcher
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
matcher.setHashIterations(1); //配置加密次数
matcher.setHashAlgorithmName("md5"); //加密方式
return matcher;
}
@Bean(name = "customRealm")
public CustomRealm customRealm(HashedCredentialsMatcher matcher){
CustomRealm customRealm = new CustomRealm();
customRealm.setCredentialsMatcher(matcher); //为自定义realm设置加密方式
return customRealm;
}
@Bean(name = "securityManager")
public SecurityManager securityManager(@Qualifier("customRealm")CustomRealm customRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(customRealm); //为securityManager配置realm
return securityManager;
}
CustomRealm 实现AuthorizingRealm,重点看doGetAuthenticationInfo方法
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
User user = userService.getUserByUsername(token.getUsername());
SimpleAuthenticationInfo simpleAuthenticationInfo = null;
if (user != null) {
try {
simpleAuthenticationInfo = new SimpleAuthenticationInfo(user, user.getPassword(), getName());
simpleAuthenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(user.getUsername())); //设置加盐。一般为随机数字,这里求方便,用用户名做盐值
} catch (Exception e) {
e.printStackTrace();
}
return simpleAuthenticationInfo;
} else {
return null;
}
}
用main方法生成一个盐值
public static void main(String[] args) {
Md5Hash admin = new Md5Hash("123", "gxd"); //“123”表示密码;"gxd"表示盐值,一遍用随机数,这里求方便,用用户名代替
System.out.println(admin);
}
进入到Md5Hash的构造方法
public Md5Hash(Object source, Object salt) {
super("MD5", source, salt);
}
3、sessionManager
1、配置
pom.xml
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.2</version>
</dependency>
application.yml 配置redis数据源
redis:
database: 10
port: 6379
timeout: 0
host: http://127.0.0.0
2、编写SessionDao。对session进行CRUD
@Component
public class SessionDao extends AbstractSessionDAO {
@Resource
private JedisUtil jedisUtil;
private final String SHIRO_SESSION_PREFIX = "shiro-session";
private byte[] getKey(String key) {
return (SHIRO_SESSION_PREFIX + key).getBytes();
}
@Override
protected Serializable doCreate(Session session) {
Serializable sessionId = generateSessionId(session);
assignSessionId(session, sessionId);
saveSession(session);
return sessionId;
}
private void saveSession(Session session) {
if (session != null && session.getId() != null) {
byte[] key = getKey(session.getId().toString());
byte[] value = SerializationUtils.serialize(session);
System.out.println("key:"+key+"value:"+value);
jedisUtil.set(key, value);
jedisUtil.expire(key, 600);
}
}
@Override
protected Session doReadSession(Serializable sessionId) {
if (sessionId == null) {
return null;
}
byte[] key = getKey(sessionId.toString());
byte[] value = jedisUtil.get(key);
return (Session) SerializationUtils.deserialize(value);
}
@Override
public void update(Session session) throws UnknownSessionException {
saveSession(session);
}
@Override
public void delete(Session session) {
if (session == null && session.getId() == null) {
return;
}
byte[] key = getKey(session.getId().toString());
jedisUtil.del(key);
}
@Override
public Collection<Session> getActiveSessions() {
Set<byte[]> keys = jedisUtil.keys(SHIRO_SESSION_PREFIX);
HashSet<Session> sessions = new HashSet<>();
if (CollectionUtils.isEmpty(keys)) {
return sessions;
}
for (byte[] key : keys) {
Session session = (Session) SerializationUtils.deserialize(jedisUtil.get(key));
sessions.add(session);
}
return sessions;
}
}
3 、编写自定义SessionManager。如果采用shiro自带的shiro将会多次访问redis,redis的压力很大,所以,在自定义sessionManager中,将session存入request中,避免了此类问题。
public class CustomSessionManage extends DefaultWebSessionManager {
@Override
protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException {
Serializable sessionId = getSessionId(sessionKey);
ServletRequest request = null;
if (sessionKey instanceof WebSessionKey) {
request = ((WebSessionKey) sessionKey).getServletRequest();
}
if (request != null && sessionId != null) {
Session session = (Session) request.getAttribute(sessionId.toString());
if (session != null) {
return session;
}
}
Session session = super.retrieveSession(sessionKey);
if (request != null && sessionId != null) {
request.setAttribute(sessionId.toString(), session);
}
return session;
}
}
4、 shiroConfig
向spring中注入jedis。
@Bean
public JedisPool getJedisPool() {
JedisPoolConfig config = new JedisPoolConfig(); //config里面可以配置很多信息,大家可以根据需要添加,这里只用最简单的
JedisPool pool = new JedisPool(config,"47.98.149.58",6379,200,"gxdredis!"); --可设置密码
return pool;
}
//如果你是本地测试,可以采用最简单的配置
@Bean
public JedisPool jedisPool(){
JedisPool jedisPool = new JedisPool();
return jedisPool;
}
向spring中注入sessionManager,可以使用自定义sessionManager,并往sessionManager中注入sessionDao
@Bean(name = "sessionManager")
public SessionManager sessionManager(SessionDao sessionDao){
CustomSessionManage customSessionManage = new CustomSessionManage();
customSessionManage.setSessionDAO(sessionDao);
return customSessionManage;
}
将sessionManager注入到securityManager中
@Bean(name = "securityManager")
public SecurityManager securityManager(@Qualifier("sessionManager") SessionManager sessionManager){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setSessionManager(sessionManager);
return securityManager;
}
5、工具类jedisUtil
@Component
public class JedisUtil {
@Resource
public JedisPool jedisPool;
private Jedis getResource(){
return jedisPool.getResource();
}
public byte[] set(byte[] key, byte[] value) {
Jedis jedis = getResource();
try {
jedis.set(key,value);
return value;
} finally {
jedis.close();
}
}
public void expire(byte[] key, int i) {
Jedis jedis = getResource();
try {
jedis.expire(key,i);
} finally {
jedis.close();
}
}
public byte[] get(byte[] key) {
Jedis resource = getResource();
try {
return resource.get(key);
} finally {
resource.close();
}
}
public void del(byte[] key) {
Jedis resource = getResource();
try {
resource.del(key);
} finally {
resource.close();
}
}
public Set<byte[]> keys(String shiro_session_prefix) {
Jedis resource = getResource();
try {
return resource.keys((shiro_session_prefix+"*").getBytes());
} finally {
resource.close();
}
}
}
打开redis-cli,查看session
127.0.0.1:6379> keys *
1) "shiro-sessioncfa3d0a5-6010-423c-8979-dbdf953685c8"
2) "shiro-sessioncafcd0a9-f4cd-41aa-9d39-e2a9fa543c03"
3) "name"
4 、CacheManager
RedisCache
@Component
public class RedisCache<K,V> implements Cache<K,V> {
private final String CACHE_PREFIX = "shiro-cache";
@Autowired
private JedisUtil jedisUtil;
@Override
public V get(K k) throws CacheException {
System.out.println("从Redis中获取权限数据");
byte[] value = jedisUtil.get(getKey(k));
if (value != null) {
return (V) SerializationUtils.deserialize(value);
}
return null;
}
@Override
public V put(K k, V v) throws CacheException {
byte[] key = getKey(k);
byte[] value = SerializationUtils.serialize(v);
jedisUtil.set(key, value);
jedisUtil.expire(key, 600);
return v;
}
@Override
public V remove(K k) throws CacheException {
byte[] key = getKey(k);
byte[] value = SerializationUtils.serialize(key);
jedisUtil.del(key);
if (value != null) {
return (V) SerializationUtils.deserialize(value);
}
return null;
}
@Override
public void clear() throws CacheException {
}
@Override
public int size() {
return 0;
}
@Override
public Set<K> keys() {
return null;
}
@Override
public Collection<V> values() {
return null;
}
private byte[] getKey(K k) {
if (k instanceof String) {
return (CACHE_PREFIX + k).getBytes();
}
return SerializationUtils.serialize(k);
}
}
RedisCacheManager
public class RedisCacheManager implements CacheManager {
@Resource
private RedisCache redisCache;
@Override
public <K, V> Cache<K, V> getCache(String s) throws CacheException {
return redisCache;
}
}
shiroConfig
@Bean(name = "redisCacheManager")
public RedisCacheManager redisCacheManager(){
return new RedisCacheManager();
}
@Bean(name = "securityManager")
public SecurityManager securityManager(@Qualifier("redisCacheManager") RedisCacheManager redisCacheManager){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setCacheManager(redisCacheManager);
return securityManager;
}
controller测试
正常情况下,首次查询,会去数据库,第二次开始, 直接从redis缓存中获取
if (subject.hasRole("user")){
return "有user权限";
}else {
return "没有权限";
}
5、RememberMe
shiroConfig
@Bean //为SimpleCookie设值,并获取SimpleCookie
public SimpleCookie rememberMeCookie() {
//这个参数是cookie的名称,对应前端的checkbox的name = rememberMe
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
//<!-- 记住我cookie生效时间30天 ,单位秒;-->
simpleCookie.setMaxAge(259200);
return simpleCookie;
}
@Bean //为CookieRememberMeManager设置SimpleCookie
public CookieRememberMeManager rememberMeManager() {
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCookie(rememberMeCookie());
return cookieRememberMeManager;
}
@Bean(name = "securityManager")
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRememberMeManager(rememberMeManager());
return securityManager;
}
controller
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(),user.getPassword());
token.setRememberMe(user.isRememberMe());
subject.login(token);
user类
private boolean rememberMe;
public boolean isRememberMe() {
return rememberMe;
}
public void setRememberMe(boolean rememberMe) {
this.rememberMe = rememberMe;
}
前端
<form action="/user/login" method="post">
用户名:<input type="text" name="username">
密码:<input type="password" name="password">
记住我:<input type="checkbox" name="rememberMe">
<input type="submit" value="登录">
</form>
检验
6、springboot使用注解实现shiro的权限控制
1、shiroConfig
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor
= new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
@Bean
@ConditionalOnMissingBean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
defaultAAP.setProxyTargetClass(true);
return defaultAAP;
}
2、pom.xml中加
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
3、application配置
**如果是application.properties**
spring.aop.proxy-target-class=true
**如果是application.yml**
aop:
proxy-target-class: true
4、controller验证
@ResponseBody
@RequestMapping("/test1")
public String test1(){
return "test1";
}
@ResponseBody
@RequiresPermissions("/test2")
@RequestMapping("/test2")
public String test(){
return "test2";
}
结果:test1方法可以访问,但是test2报错“Subject does not have permission ”错误