至於什麼是Spring security ,主要兩個做用,用戶認證和受權。即咱們常說的,用戶只有登陸了才能進行其餘操做,沒有登陸的話就重定向到登陸界面。有的用戶有權限執行某一操做,而有的用戶不能執行則是受權。算是一個項目安全框架。和shiro 框架同樣。兩者的不一樣你們能夠百度小。Spring security 是Spring家族的一員,因此Springboot算是對Spring security 進行的自然的支持。java
之因此這樣說,spring security 被人詬病的配置繁瑣複雜,在springboot中變的簡單起來。若是咱們只是demo 效果,能夠作到0配置實現。mysql
下面咱們就一塊兒來見識一下吧linux
咱們在pom.xml 文件中引入Spring security 的stattergit
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
咱們先來0配置的看看。引入依賴之後,咱們建立一個HelloController 內容以下:程序員
@RestController public class HelloController { @RequestMapping("/hello") public String hello(){ return "hello world"; } }
而後咱們啓動項目,按照咱們正常的理解,直接訪問github
localhost:8080/hello
會返回hello world 。但結果倒是重定向到了/login 。下面的界面是Spring security 自帶的。
其實上面能夠看到,Spring security 已經起做用了,沒有登陸不能訪問 /hello 接口。web
默認的用戶名爲 user;
密碼在咱們項目啓動時控制檯有打印,每次都會不同,隨機生成的。
咱們輸入帳號密碼,再試試
能夠看到,在登陸以後,咱們在請求 /hello 會直接返回hello world , 那是否是隻要登陸一次,後面就能夠一直訪問呢?固然不是的,登陸成功以後,會將信息保存在session 中,再登陸的時候,就會經過session 校驗,這樣就能夠訪問到了,當session過時獲取咱們手動清理掉後,就須要從新登陸了。咱們來試試。打開控制檯,application 中的cookies 中的jsessionid 清理掉。
咱們接着請求試試,能夠發現刪除後,就會從新回到登陸界面。
spring
上面咱們使用的默認的用戶名和密碼,可是實際上咱們確定不會這麼作的,上面只是說明springboot 徹底的集成了Spring security 。下面咱們先來簡單的配置用戶名密碼,之因此這樣說,由於咱們實際過程當中應該仍是不會這麼用的。之因此要講,讓你們瞭解的更全面,也爲下面鋪墊。sql
首先咱們來簡單的,咱們能夠直接在application.properties 中配置用戶名和密碼。來代替默認用戶名和密碼的效果。數據庫
spring.security.user.name=quellanan spring.security.user.password=123456 spring.security.user.roles=admin
分別是設置用戶名,密碼,角色。咱們這裏暫時只用了用戶認證,因此角色設不設置無所謂。配置好這些以後咱們重啓項目在界面上試試再。
沒有問題,可是沒有什麼用,咱們實際中是不會這麼幹的吧。
在內存中配置的話,相對來講要複雜點,咱們建立一個config 包,在包下建立SecurityConfig 類繼承 WebSecurityConfigurerAdapter
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .passwordEncoder(passwordEncoder()) // 指定加密方式 .withUser("qaz").password(passwordEncoder().encode("123456")).roles("admin") .and() .withUser("test").password(passwordEncoder().encode("123456")).roles("USER"); } @Bean public PasswordEncoder passwordEncoder() { // BCryptPasswordEncoder:Spring Security 提供的加密工具 return new BCryptPasswordEncoder(); } }
這裏咱們重寫了configure(AuthenticationManagerBuilder auth) 方法,就是將定義的用戶配置到內存中。這裏有一個問題須要說明一下,就是這裏配置的話,密碼須要用BCryptPasswordEncoder 加密。若是不加密的話,項目編譯啓動不會報錯,可是登錄的時候就會提示帳號密碼錯誤。
還有一個問題就是,若是咱們在這配置了,那咱們在application.peoperties 中配置的就會失效。
上面說的這兩種方法,其實都是不經常使用的,咱們在實際項目中根本不會在項目中寫死用戶信息的。基本上都是存在數據庫中。因此下面咱們就開始講解咱們最經常使用的模式吧。
因爲這一類,涉及的較多,就單獨一級標題出來,不放在二級標題裏面了。
既然是用到數據庫,項目中天然要引入數據的配置啦,我這裏用的是mysql 和mybatis.
這是整個項目成型後的目錄結構,先放出來,你們內心有底,而後一步一步的來。
簡單的三張表,user,roles,roles_user 。
下面是 sql。直接執行就能夠
/* Date: 2017-12-26 18:36:12 */ CREATE DATABASE `quellanan` DEFAULT CHARACTER SET utf8; USE `quellanan`; SET FOREIGN_KEY_CHECKS=0; -- ---------------------------- -- Table structure for roles -- ---------------------------- DROP TABLE IF EXISTS `roles`; CREATE TABLE `roles` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(32) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of roles -- ---------------------------- INSERT INTO `roles` VALUES ('1', '超級管理員'); INSERT INTO `roles` VALUES ('2', '普通用戶'); INSERT INTO `roles` VALUES ('3', '測試角色1'); INSERT INTO `roles` VALUES ('4', '測試角色2'); INSERT INTO `roles` VALUES ('5', '測試角色3'); -- ---------------------------- -- Table structure for roles_user -- ---------------------------- DROP TABLE IF EXISTS `roles_user`; CREATE TABLE `roles_user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `rid` int(11) DEFAULT '2', `uid` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `rid` (`rid`), KEY `roles_user_ibfk_2` (`uid`), CONSTRAINT `roles_user_ibfk_1` FOREIGN KEY (`rid`) REFERENCES `roles` (`id`), CONSTRAINT `roles_user_ibfk_2` FOREIGN KEY (`uid`) REFERENCES `user` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB AUTO_INCREMENT=131 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of roles_user -- ---------------------------- INSERT INTO `roles_user` VALUES ('1', '1', '1'); INSERT INTO `roles_user` VALUES ('2', '2', '2'); INSERT INTO `roles_user` VALUES ('3', '3', '3'); INSERT INTO `roles_user` VALUES ('4', '1', '4'); -- ---------------------------- -- Table structure for user -- ---------------------------- DROP TABLE IF EXISTS `user`; CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(64) DEFAULT NULL, `nickname` varchar(64) DEFAULT NULL, `password` varchar(255) DEFAULT NULL, `enabled` tinyint(1) DEFAULT '1', `email` varchar(64) DEFAULT NULL, `userface` varchar(255) DEFAULT NULL, `regTime` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of user -- ---------------------------- INSERT INTO `user` VALUES ('1', 'quellanan', '', '$2a$10$Hv0YGLi/siOswCTP236MtOTWbClcM6rN1LCyqwfRmrwCJZqXHsj5a', '1', 'quellanan@qq.com','', '2017-12-08 09:30:22'); INSERT INTO `user` VALUES ('2', 'qaz', '', '$2a$10$6H69XLebCrGhHeHzDXEoH.0x8tMFS0XfdDPwI5s.Eu9pbqRpncA.G', '1', 'quellanan@qq.com','', '2017-12-08 09:30:22'); INSERT INTO `user` VALUES ('3', 'wsx', '', '$2a$10$6H69XLebCrGhHeHzDXEoH.0x8tMFS0XfdDPwI5s.Eu9pbqRpncA.G', '1', 'quellanan@qq.com','', '2017-12-08 09:30:22'); INSERT INTO `user` VALUES ('4', 'test', '', '$2a$10$6H69XLebCrGhHeHzDXEoH.0x8tMFS0XfdDPwI5s.Eu9pbqRpncA.G', '1', 'quellanan@qq.com','', '2017-12-08 09:30:22'); SET FOREIGN_KEY_CHECKS=1;
咱們首先在原先pom 文件基礎上增長,以下依賴。
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.0</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>
前面三個是mysql 和mybatis的依賴。lombok 是一個工具類插件。
同時咱們須要修改一下pom 文件中的build ,否則咱們項目可能會找不到mybatis 的xml文件。
<build> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> </resource> <resource> <directory>src/main/resources</directory> </resource> </resources> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://127.0.0.1:3306/quellanan?allowMultiQueries=true&useUnicode=true&characterEncoding=utf8&serverTimezone=UTC spring.datasource.username=root spring.datasource.password=123456 spring.datasource.max-idle=10 spring.datasource.max-wait=10000 spring.datasource.min-idle=5 spring.datasource.initial-size=5
這裏若是想要打印mybatis 的sql 日誌。能夠添加一個mybatis-config.xml文件,和application.properties 同目錄
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <settings> <setting name="logImpl" value="STDOUT_LOGGING" /> </settings> </configuration>
並在application.properties 中加上
mybatis.config-location=classpath:/mybatis-config.xml
咱們在entry 包下建立 RoleEntry。代碼以下:
@Getter @Setter public class RoleEntry { private Long id; private String name; }
咱們在建立 UserEntry ,可是UserEntry 比較特殊,由於咱們須要使用Spring security 。因此這裏,UserEntry 須要實現 UserDetails。
代碼以下:
@Setter @Getter public class UserEntry implements UserDetails { private Long id; private String username; private String password; private String nickname; private boolean enabled; private List<RoleEntry> roles; private String email; private String userface; private Timestamp regTime; /** * 獲取角色權限 * @return */ @Override public Collection<? extends GrantedAuthority> getAuthorities() { List<GrantedAuthority> authorities = new ArrayList<>(); for (RoleEntry role : roles) { authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getName())); } return authorities; } /** * 獲取密碼 * @return */ @Override public String getPassword() { return password; } /** * 獲取用戶名 * @return */ @Override public String getUsername() { return username; } /** * 用戶帳號是否過時 */ @Override public boolean isAccountNonExpired() { return true; } /** * 用戶帳號是否被鎖定 */ @Override public boolean isAccountNonLocked() { return true; } /** * 用戶密碼是否過時 */ @Override public boolean isCredentialsNonExpired() { return true; } /** * 用戶是否可用 */ @Override public boolean isEnabled() { return enabled; } }
能夠看到,基本上都是重寫的方法。也比較簡單。
這裏我將xml 文件和接口放在一塊兒了,大家也能夠在resources 中建立一個mapper,將xml 文件放在哪裏。
mapper層沒有什麼好說的,是mybatis 的一些知識,咱們這裏講代碼貼出來。
@Mapper public interface RolesMapper { List<RoleEntry> getRolesByUid(Long uid); }
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.zlflovemm.security.mapper.RolesMapper"> <select id="getRolesByUid" parameterType="long" resultType="com.zlflovemm.security.entry.RoleEntry"> SELECT r.* FROM roles r,roles_user ru WHERE r.`id`=ru.`rid` AND ru.`uid`=#{uid} </select> </mapper>
@Mapper public interface UserMapper { UserEntry loadUserByUsername(@Param("username") String username); }
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.zlflovemm.security.mapper.UserMapper"> <select id="loadUserByUsername" resultType="com.zlflovemm.security.entry.UserEntry"> SELECT * FROM user WHERE username=#{username} </select> </mapper>
在service 層咱們要注意一點,咱們須要實現 UserDetailsService 接口。
咱們先建立一個UserService 繼承 UserDetailsService。而後建立一個UserServiceImpl 來時實現UserService 從而達到實現UserDetailsService的目的。這樣作是爲了保證項目結構的統一層次。
public interface UserService extends UserDetailsService { }
@Service @Slf4j @Transactional public class UserServiceImpl implements UserService { @Autowired UserMapper userMapper; @Autowired RolesMapper rolesMapper; @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { UserEntry user = userMapper.loadUserByUsername(s); if (user == null) { //避免返回null,這裏返回一個不含有任何值的User對象,在後期的密碼比對過程當中同樣會驗證失敗 return new UserEntry(); } //查詢用戶的角色信息,並返回存入user中 List<RoleEntry> roles = rolesMapper.getRolesByUid(user.getId()); user.setRoles(roles); return user; } }
能夠看到,主要是爲了實現 loadUserByUsername的方法。在這個方法中咱們 loadUserByUsername和getRolesByUid 就是咱們在mapper 定義的查詢數據庫數據的方法。
前面作了這麼多,其實都是準備工做,主要的目的就是提供一個Bean 。作完上面這些,咱們再回到 SecurityConfig 中,其實咱們如今須要修改的不多了。
咱們將用戶寫在內存的方法註釋掉。經過數據庫查詢。
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired UserService userService; @Bean public PasswordEncoder passwordEncoder() { // BCryptPasswordEncoder:Spring Security 提供的加密工具 return new BCryptPasswordEncoder(); } @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userService) .passwordEncoder(passwordEncoder());//passwoldEncoder是對密碼的加密處理,若是user中密碼沒有加密,則能夠不加此方法。注意加密請使用security自帶的加密方式。 } }
能夠和開始的 SecurityConfig 文件對比下,其實你就是多了一個userService,而後configure(AuthenticationManagerBuilder auth)中是經過userService 進行校驗的。
好了,其實到這裏,咱們就已經完成了,咱們啓動項目,就能夠看到和以前寫在內存中達到同樣的效果。
覺得到這就完了,其實還有一點哈哈。咱們如今是全部的接口都須要先登陸才能訪問,沒有登陸的話就跳轉到login界面。實際上咱們確定有些是不須要認證也能夠訪問的,好比如下靜態文件或者註冊的請求。
因此咱們仍是要配置一下過濾。
其實也很簡單,同樣的在 SecurityConfig 文件中 重寫 configure(HttpSecurity http) 方法。
這裏我直接參考官網上的。
https://spring.io/guides/gs/securing-web/
該configure(HttpSecurity)方法定義應保護哪些URL路徑,不該該保護哪些URL路徑。具體而言,「 /」和「 / home」路徑配置爲不須要任何身份驗證。全部其餘路徑必須通過驗證。
用戶成功登陸後,他們將被重定向到以前要求身份驗證的頁面。有一個由指定的自定義「 /登陸」頁面loginPage(),每一個人均可以查看它。
咱們代碼中 把 loginPage("/login") 註釋掉就行了,若是不註釋的話,就須要咱們本身寫login 界面和請求。咱們這裏就用框架自帶的。
@Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/", "/hello").permitAll() .anyRequest().authenticated() .and() .formLogin() //.loginPage("/login") .permitAll() .and() .logout() .permitAll(); }
這樣配置就說明 /hell 和 / 請求不會攔截,其餘的請求,須要先登陸才能訪問。
爲了更方便的看到效果,咱們在HelloController 中再加兩個方法
@RequestMapping("/hello2") public String hello2(){ return "hello adada"; } @RequestMapping("/") public String hello3(){ return " qazqeee"; } }
如今咱們啓動來看下效果。
證實咱們配置的過濾是有效果的。
到此算是差很少結束了,其實還有不少知識點,不是一篇文章能講完的,這裏算是拋轉引玉,但願對你們有幫助。後面我也會持續更新
好了,源碼我上傳到github 上啦
https://github.com/QuellanAn/security
後續加油♡
歡迎你們關注我的公衆號 "程序員愛酸奶"
分享各類學習資料,包含java,linux,大數據等。資料包含視頻文檔以及源碼,同時分享本人及投遞的優質技術博文。
若是你們喜歡記得關注和分享喲❤