個人我的博客:html
個人公衆號:菜鳥小謝java
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-starter</artifactId> <version>1.5.3</version> </dependency>
@Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean(SecurityManager securityManager){ //建立shiro的filter ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); //注入安全管理器 shiroFilterFactoryBean.setSecurityManager(securityManager); return shiroFilterFactoryBean; }
@Bean public DefaultWebSecurityManager getSecurityManager(Realm realm){ DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(); defaultWebSecurityManager.setRealm(realm); return defaultWebSecurityManager; }
public class CustomerRealm extends AuthorizingRealm { //處理受權 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { return null; } //處理認證 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { return null; } }
//建立自定義realm @Bean public Realm getRealm(){ return new CustomerRealm(); }
@Controller public class IndexController { @RequestMapping("index") public String index(){ System.out.println("跳轉至主頁"); return "index"; } }
注意:mysql
修改ShiroFilterFactoryBean配置redis
//注入安全管理器 shiroFilterFactoryBean.setSecurityManager(securityManager); Map<String,String> map = new LinkedHashMap<>(); map.put("/**","authc"); //配置認證和受權規則 shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
配置縮寫 | 對應的過濾器 | 功能 |
---|---|---|
anon | AnonymousFilter | 指定url能夠匿名訪問 |
authc | FormAuthenticationFilter | 指定url須要form表單登陸,默認會從請求中獲取username 、password ,rememberMe 等參數並嘗試登陸,若是登陸不了就會跳轉到loginUrl配置的路徑。咱們也能夠用這個過濾器作默認的登陸邏輯,可是通常都是咱們本身在控制器寫登陸邏輯的,本身寫的話出錯返回的信息均可以定製嘛。 |
authcBasic | BasicHttpAuthenticationFilter | 指定url須要basic登陸 |
logout | LogoutFilter | 登出過濾器,配置指定url就能夠實現退出功能,很是方便 |
noSessionCreation | NoSessionCreationFilter | 禁止建立會話 |
perms | PermissionsAuthorizationFilter | 須要指定權限才能訪問 |
port | PortFilter | 須要指定端口才能訪問 |
rest | HttpMethodPermissionFilter | 將http請求方法轉化成相應的動詞來構造一個權限字符串,這個感受意義不大,有興趣本身看源碼的註釋 |
roles | RolesAuthorizationFilter | 須要指定角色才能訪問 |
ssl | SslFilter | 須要https請求才能訪問 |
user | UserFilter | 須要已登陸或「記住我」的用戶才能訪問 |
<form action="${pageContext.request.contextPath}/user/login" method="post"> 用戶名:<input type="text" name="username" > <br/> 密碼 : <input type="text" name="password"> <br> <input type="submit" value="登陸"> </form>
@Controller @RequestMapping("user") public class UserController { /** * 用來處理身份認證 * @param username * @param password * @return */ @RequestMapping("login") public String login(String username,String password){ //獲取主體對象 Subject subject = SecurityUtils.getSubject(); try { subject.login(new UsernamePasswordToken(username,password)); return "redirect:/index.jsp"; } catch (UnknownAccountException e) { e.printStackTrace(); System.out.println("用戶名錯誤!"); }catch (IncorrectCredentialsException e){ e.printStackTrace(); System.out.println("密碼錯誤!"); } return "redirect:/login.jsp"; } }
@Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("=========================="); String principal = (String) token.getPrincipal(); if("xiaochen".equals(principal)){ return new SimpleAuthenticationInfo(principal,"123",this.getName()); } return null; } }
@Controller @RequestMapping("user") public class UserController { /** * 退出登陸 * */ @RequestMapping("logout") public String logout(){ Subject subject = SecurityUtils.getSubject(); subject.logout();//退出用戶 return "redirect:/login.jsp"; } }
<h1>用戶註冊</h1> <form action="${pageContext.request.contextPath}/user/register" method="post"> 用戶名:<input type="text" name="username" > <br/> 密碼 : <input type="text" name="password"> <br> <input type="submit" value="當即註冊"> </form>
SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for t_user -- ---------------------------- DROP TABLE IF EXISTS `t_user`; CREATE TABLE `t_user` ( `id` int(6) NOT NULL AUTO_INCREMENT, `username` varchar(40) DEFAULT NULL, `password` varchar(40) DEFAULT NULL, `salt` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; SET FOREIGN_KEY_CHECKS = 1;
<!--mybatis相關依賴--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.2</version> </dependency> <!--mysql--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.38</version> </dependency> <!--druid--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.19</version> </dependency>
server.port=8888 server.servlet.context-path=/shiro spring.application.name=shiro spring.mvc.view.prefix=/ spring.mvc.view.suffix=.jsp #新增配置 spring.datasource.type=com.alibaba.druid.pool.DruidDataSource spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/shiro?characterEncoding=UTF-8 spring.datasource.username=root spring.datasource.password=root mybatis.type-aliases-package=com.baizhi.springboot_jsp_shiro.entity mybatis.mapper-locations=classpath:com/baizhi/mapper/*.xml
@Data @Accessors(chain = true) @AllArgsConstructor @NoArgsConstructor public class User { private String id; private String username; private String password; private String salt; }
@Mapper public interface UserDAO { void save(User user); }
<insert id="save" parameterType="User" useGeneratedKeys="true" keyProperty="id"> insert into t_user values(#{id},#{username},#{password},#{salt}) </insert>
public interface UserService { //註冊用戶方法 void register(User user); }
public class SaltUtils { /** * 生成salt的靜態方法 * @param n * @return */ public static String getSalt(int n){ char[] chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890!@#$%^&*()".toCharArray(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < n; i++) { char aChar = chars[new Random().nextInt(chars.length)]; sb.append(aChar); } return sb.toString(); } }
@Service @Transactional public class UserServiceImpl implements UserService { @Autowired private UserDAO userDAO; @Override public void register(User user) { //處理業務調用dao //1.生成隨機鹽 String salt = SaltUtils.getSalt(8); //2.將隨機鹽保存到數據 user.setSalt(salt); //3.明文密碼進行md5 + salt + hash散列 Md5Hash md5Hash = new Md5Hash(user.getPassword(),salt,1024); user.setPassword(md5Hash.toHex()); userDAO.save(user); } }
@Controller @RequestMapping("user") public class UserController { @Autowired private UserService userService; /** * 用戶註冊 */ @RequestMapping("register") public String register(User user) { try { userService.register(user); return "redirect:/login.jsp"; }catch (Exception e){ e.printStackTrace(); return "redirect:/register.jsp"; } } }
@Mapper public interface UserDAO { void save(User user); //根據身份信息認證的方法 User findByUserName(String username); }
<select id="findByUserName" parameterType="String" resultType="User"> select id,username,password,salt from t_user where username = #{username} </select>
public interface UserService { //註冊用戶方法 void register(User user); //根據用戶名查詢業務的方法 User findByUserName(String username); }
@Service("userService") @Transactional public class UserServiceImpl implements UserService { @Autowired private UserDAO userDAO; @Override public User findByUserName(String username) { return userDAO.findByUserName(username); } }
@Component public class ApplicationContextUtils implements ApplicationContextAware { private static ApplicationContext context; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.context = applicationContext; } //根據bean名字獲取工廠中指定bean 對象 public static Object getBean(String beanName){ return context.getBean(beanName); } }
@Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("=========================="); //根據身份信息 String principal = (String) token.getPrincipal(); //在工廠中獲取service對象 UserService userService = (UserService) ApplicationContextUtils.getBean("userService"); //根據身份信息查詢 User user = userService.findByUserName(principal); if(!ObjectUtils.isEmpty(user)){ //返回數據庫信息 return new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(), ByteSource.Util.bytes(user.getSalt()),this.getName()); } return null; }
@Bean public Realm getRealm(){ CustomerRealm customerRealm = new CustomerRealm(); //設置hashed憑證匹配器 HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(); //設置md5加密 credentialsMatcher.setHashAlgorithmName("md5"); //設置散列次數 credentialsMatcher.setHashIterations(1024); customerRealm.setCredentialsMatcher(credentialsMatcher); return customerRealm; }
<%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %> <shiro:hasAnyRoles name="user,admin"> <li><a href="">用戶管理</a> <ul> <shiro:hasPermission name="user:add:*"> <li><a href="">添加</a></li> </shiro:hasPermission> <shiro:hasPermission name="user:delete:*"> <li><a href="">刪除</a></li> </shiro:hasPermission> <shiro:hasPermission name="user:update:*"> <li><a href="">修改</a></li> </shiro:hasPermission> <shiro:hasPermission name="user:find:*"> <li><a href="">查詢</a></li> </shiro:hasPermission> </ul> </li> </shiro:hasAnyRoles> <shiro:hasRole name="admin"> <li><a href="">商品管理</a></li> <li><a href="">訂單管理</a></li> <li><a href="">物流管理</a></li> </shiro:hasRole>
@RequestMapping("save") public String save(){ System.out.println("進入方法"); //獲取主體對象 Subject subject = SecurityUtils.getSubject(); //代碼方式 if (subject.hasRole("admin")) { System.out.println("保存訂單!"); }else{ System.out.println("無權訪問!"); } //基於權限字符串 //.... return "redirect:/index.jsp"; }
@RequiresRoles(value={"admin","user"})//用來判斷角色 同時具備 admin user @RequiresPermissions("user:update:01") //用來判斷權限字符串 @RequestMapping("save") public String save(){ System.out.println("進入方法"); return "redirect:/index.jsp"; }
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-fu4xzEf1-1591145976800)(Shiro 實戰教程.assets/image-20200527204839080.png)]算法
SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for t_pers -- ---------------------------- DROP TABLE IF EXISTS `t_pers`; CREATE TABLE `t_pers` ( `id` int(6) NOT NULL AUTO_INCREMENT, `name` varchar(80) DEFAULT NULL, `url` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- ---------------------------- -- Table structure for t_role -- ---------------------------- DROP TABLE IF EXISTS `t_role`; CREATE TABLE `t_role` ( `id` int(6) NOT NULL AUTO_INCREMENT, `name` varchar(60) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- ---------------------------- -- Table structure for t_role_perms -- ---------------------------- DROP TABLE IF EXISTS `t_role_perms`; CREATE TABLE `t_role_perms` ( `id` int(6) NOT NULL, `roleid` int(6) DEFAULT NULL, `permsid` int(6) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- ---------------------------- -- Table structure for t_user -- ---------------------------- DROP TABLE IF EXISTS `t_user`; CREATE TABLE `t_user` ( `id` int(6) NOT NULL AUTO_INCREMENT, `username` varchar(40) DEFAULT NULL, `password` varchar(40) DEFAULT NULL, `salt` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; -- ---------------------------- -- Table structure for t_user_role -- ---------------------------- DROP TABLE IF EXISTS `t_user_role`; CREATE TABLE `t_user_role` ( `id` int(6) NOT NULL, `userid` int(6) DEFAULT NULL, `roleid` int(6) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; SET FOREIGN_KEY_CHECKS = 1;
//根據用戶名查詢全部角色 User findRolesByUserName(String username); //根據角色id查詢權限集合 List<Perms> findPermsByRoleId(String id);
<resultMap id="userMap" type="User"> <id column="uid" property="id"/> <result column="username" property="username"/> <!--角色信息--> <collection property="roles" javaType="list" ofType="Role"> <id column="id" property="id"/> <result column="rname" property="name"/> </collection> </resultMap> <select id="findRolesByUserName" parameterType="String" resultMap="userMap"> SELECT u.id uid,u.username,r.id,r.NAME rname FROM t_user u LEFT JOIN t_user_role ur ON u.id=ur.userid LEFT JOIN t_role r ON ur.roleid=r.id WHERE u.username=#{username} </select> <select id="findPermsByRoleId" parameterType="String" resultType="Perms"> SELECT p.id,p.NAME,p.url,r.NAME FROM t_role r LEFT JOIN t_role_perms rp ON r.id=rp.roleid LEFT JOIN t_perms p ON rp.permsid=p.id WHERE r.id=#{id} </select>
//根據用戶名查詢全部角色 User findRolesByUserName(String username); //根據角色id查詢權限集合 List<Perms> findPermsByRoleId(String id);
@Override public List<Perms> findPermsByRoleId(String id) { return userDAO.findPermsByRoleId(id); } @Override public User findRolesByUserName(String username) { return userDAO.findRolesByUserName(username); }
public class CustomerRealm extends AuthorizingRealm { @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { //獲取身份信息 String primaryPrincipal = (String) principals.getPrimaryPrincipal(); System.out.println("調用受權驗證: "+primaryPrincipal); //根據主身份信息獲取角色 和 權限信息 UserService userService = (UserService) ApplicationContextUtils.getBean("userService"); User user = userService.findRolesByUserName(primaryPrincipal); //受權角色信息 if(!CollectionUtils.isEmpty(user.getRoles())){ SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); user.getRoles().forEach(role->{ simpleAuthorizationInfo.addRole(role.getName()); //權限信息 List<Perms> perms = userService.findPermsByRoleId(role.getId()); if(!CollectionUtils.isEmpty(perms)){ perms.forEach(perm->{ simpleAuthorizationInfo.addStringPermission(perm.getName()); }); } }); return simpleAuthorizationInfo; } return null; } }
<!--引入shiro和ehcache--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.5.3</version> </dependency>
//3.建立自定義realm @Bean public Realm getRealm(){ CustomerRealm customerRealm = new CustomerRealm(); //修改憑證校驗匹配器 HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(); //設置加密算法爲md5 credentialsMatcher.setHashAlgorithmName("MD5"); //設置散列次數 credentialsMatcher.setHashIterations(1024); customerRealm.setCredentialsMatcher(credentialsMatcher); //開啓緩存管理器 customerRealm.setCachingEnabled(true); customerRealm.setAuthorizationCachingEnabled(true); customerRealm.setAuthorizationCachingEnabled(true); customerRealm.setCacheManager(new EhCacheManager()); return customerRealm; }
<!--redis整合springboot--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
spring.redis.port=6379 spring.redis.host=localhost spring.redis.database=0
➜ bin ls dump.rdb redis-check-aof redis-cli redis-server redis.conf redis-benchmark redis-check-rdb redis-sentinel redis-trib.rb ➜ bin ./redis-server redis.conf
public class RedisCacheManager implements CacheManager { @Override public <K, V> Cache<K, V> getCache(String cacheName) throws CacheException { System.out.println("緩存名稱: "+cacheName); return new RedisCache<K,V>(cacheName); } }
public class RedisCache<K,V> implements Cache<K,V> { private String cacheName; public RedisCache() { } public RedisCache(String cacheName) { this.cacheName = cacheName; } @Override public V get(K k) throws CacheException { System.out.println("獲取緩存:"+ k); return (V) getRedisTemplate().opsForHash().get(this.cacheName,k.toString()); } @Override public V put(K k, V v) throws CacheException { System.out.println("設置緩存key: "+k+" value:"+v); getRedisTemplate().opsForHash().put(this.cacheName,k.toString(),v); return null; } @Override public V remove(K k) throws CacheException { return (V) getRedisTemplate().opsForHash().delete(this.cacheName,k.toString()); } @Override public v remove(k k) throws CacheException { return (v) getRedisTemplate().opsForHash().delete(this.cacheName,k.toString()); } @Override public void clear() throws CacheException { getRedisTemplate().delete(this.cacheName); } @Override public int size() { return getRedisTemplate().opsForHash().size(this.cacheName).intValue(); } @Override public Set<k> keys() { return getRedisTemplate().opsForHash().keys(this.cacheName); } @Override public Collection<v> values() { return getRedisTemplate().opsForHash().values(this.cacheName); } private RedisTemplate getRedisTemplate(){ RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate"); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); return redisTemplate; } //封裝獲取redisTemplate private RedisTemplate getRedisTemplate(){ RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate"); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); return redisTemplate; } }
解決方案: 須要自動salt實現序列化spring
自定義salt實現序列化sql
//自定義salt實現 實現序列化接口 public class MyByteSource extends SimpleByteSource implements Serializable { public MyByteSource(String string) { super(string); } }
在realm中使用自定義saltshell
@Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("=========================="); //根據身份信息 String principal = (String) token.getPrincipal(); //在工廠中獲取service對象 UserService userService = (UserService) ApplicationContextUtils.getBean("userService"); User user = userService.findByUserName(principal); if(!ObjectUtils.isEmpty(user)){ return new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(), new MyByteSource(user.getSalt()),this.getName()); } return null; }
開發控制器數據庫
@RequestMapping("getImage") public void getImage(HttpSession session, HttpServletResponse response) throws IOException { //生成驗證碼 String code = VerifyCodeUtils.generateVerifyCode(4); //驗證碼放入session session.setAttribute("code",code); //驗證碼存入圖片 ServletOutputStream os = response.getOutputStream(); response.setContentType("image/png"); VerifyCodeUtils.outputImage(220,60,os,code); }
修改認證流程apache
@RequestMapping("login") public String login(String username, String password,String code,HttpSession session) { //比較驗證碼 String codes = (String) session.getAttribute("code"); try { if (codes.equalsIgnoreCase(code)){ //獲取主體對象 Subject subject = SecurityUtils.getSubject(); subject.login(new UsernamePasswordToken(username, password)); return "redirect:/index.jsp"; }else{ throw new RuntimeException("驗證碼錯誤!"); } } catch (UnknownAccountException e) { e.printStackTrace(); System.out.println("用戶名錯誤!"); } catch (IncorrectCredentialsException e) { e.printStackTrace(); System.out.println("密碼錯誤!"); }catch (Exception e){ e.printStackTrace(); System.out.println(e.getMessage()); } return "redirect:/login.jsp"; }
修改salt不能序列化的問題
//自定義salt實現 實現序列化接口 public class MyByteSource implements ByteSource,Serializable { private byte[] bytes; private String cachedHex; private String cachedBase64; //加入無參數構造方法實現序列化和反序列化 public MyByteSource(){ } public MyByteSource(byte[] bytes) { this.bytes = bytes; } public MyByteSource(char[] chars) { this.bytes = CodecSupport.toBytes(chars); } public MyByteSource(String string) { this.bytes = CodecSupport.toBytes(string); } public MyByteSource(ByteSource source) { this.bytes = source.getBytes(); } public MyByteSource(File file) { this.bytes = (new MyByteSource.BytesHelper()).getBytes(file); } public MyByteSource(InputStream stream) { this.bytes = (new MyByteSource.BytesHelper()).getBytes(stream); } public static boolean isCompatible(Object o) { return o instanceof byte[] || o instanceof char[] || o instanceof String || o instanceof ByteSource || o instanceof File || o instanceof InputStream; } public byte[] getBytes() { return this.bytes; } public boolean isEmpty() { return this.bytes == null || this.bytes.length == 0; } public String toHex() { if (this.cachedHex == null) { this.cachedHex = Hex.encodeToString(this.getBytes()); } return this.cachedHex; } public String toBase64() { if (this.cachedBase64 == null) { this.cachedBase64 = Base64.encodeToString(this.getBytes()); } return this.cachedBase64; } public String toString() { return this.toBase64(); } public int hashCode() { return this.bytes != null && this.bytes.length != 0 ? Arrays.hashCode(this.bytes) : 0; } public boolean equals(Object o) { if (o == this) { return true; } else if (o instanceof ByteSource) { ByteSource bs = (ByteSource)o; return Arrays.equals(this.getBytes(), bs.getBytes()); } else { return false; } } private static final class BytesHelper extends CodecSupport { private BytesHelper() { } public byte[] getBytes(File file) { return this.toBytes(file); } public byte[] getBytes(InputStream stream) { return this.toBytes(stream); } } }