سلام ، امروز ادامه بخش اول رو مینویسم و به این میپردازم که چجوری از دیتابیس برای ذخیره کاربرها استفاده کنید.
برای اینکار نیازی ندارید که بدونید که برای فلان دیتابیس باید چیکارکنید و درواقع solution سکیوریتی اصلا وابسته به دیتابیس خاصی نیست. شما میتونید دیتای خودتون رو باهر ساختاری که مدنظرتون هست هرجایی که دلتون میخواد ذخیره کنید (تو فایل ، mysql ، اکسل ، منگو ....).
قلب این ماجرا دوتا interface هست به نام های UserDetails و UserDetailsService و کافیه بدونیم این دوتا interface چیکارمیکنن تا هر دیتابیسی رو بتونیم به پروژه سکیوریتی خودمون وصل کنیم.
از UserDetails شروع کینم این واسط دوراقع اطلاعاتی که سکیوریتی از مدل یوزر انتظار داره رو براش فراهم میکنه اطلاعاتی از قبیل : نام کاربری ، پسورد ، نقشها ، فعال بودن و چنتا چیز دیگه که البته میتونید همه رو بهش پاس ندید و چیزای اصلی رو فقط پیاده کنید ، منم اینجا خیلیاشو بهش مقدار پیش فرض میدم و پیاده نمیکنم. قبل از اینکه پیاده سازی این واسط رو ببینیم بریم مدل یوزرمون رو تعریف کنیم.
من از mysql استفاده کردم اما آخر این آموزش وقتی همه پیاده سازی هارو دیدید متوجه میشید که چجوری میتونید با هرچیزی این واسط هارو پیاده کنید
پیاده سازی مدل User من به این شکله :
@Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false) private String username; @Column(nullable = false) private String password; private int active; private String roles; private String authorities; public User(String username, String password, String roles, String authorities) { this.username = username; this.password = password; this.roles = roles; this.authorities = authorities; active = 1; } protected User() { } public List<String> getAuthoritiesList() { if (!this.authorities.isBlank()) return Arrays.asList(this.authorities.split(",")); return new ArrayList<>(); } public List<String> getRolesList() { if (!this.roles.isBlank()) return Arrays.asList(this.roles.split(",")); return new ArrayList<>(); } }
من برای اختصار getter و setter هارو حذف کردم. دوتا متد اضافه ام برای گرفتن لیست authority ها و role ها نوشتم چون دارن در دیتابیس بصورت comma separated ذخیره میشن این دوتا رو نوشتم جهت تبدیلشون به لیست.
خب طبق روال Spring Data هر مدلی برای صحبت با دیتابیس نیاز به Repository داره که اونم تعریف میکنیم.
@Repository public interface UserRepo extends JpaRepository<User, Long> { User findByUsername(String username); }
فقط یک متد برای گرفتن یوزر با username گذاشتیم که بعدا باهاش کارداریم.
حالا آماده ایم که واسط UserDetails را پیاده سازی کنیم. فقط هول نکنید :) خیلی سادس و قدم به قدم میگم که داریم چیکارمیکنیم و پیشنهاد میکنم اول کد رو نخونید و اول توضیحات زیرشو بخونید و همزمان ببینید کدش چیه.
public class UserPrincipal implements UserDetails { final private User user; public UserPrincipal(User user) { this.user = user; } /** * In this method we've mixed user's roles and authorities because in Spring security * both of these store in a collection with different name convention that roles have ROLE_ in the first of string * * @return Collection */ @Override public Collection<? extends GrantedAuthority> getAuthorities() { List<GrantedAuthority> authorities = new ArrayList<>(); this.user.getAuthoritiesList().forEach(authority -> { GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(authority); authorities.add(grantedAuthority); }); this.user.getRolesList().forEach(role -> { GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_" + role); authorities.add(grantedAuthority); }); return authorities; } @Override public String getPassword() { return this.user.getPassword(); } @Override public String getUsername() { return this.user.getUsername(); } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return this.user.getActive() == 1; } }
خب اسم کلاس رو گذاشتیم UserPrincipal ، چون یوزری که لاگین شده در سیستم میگن principal.
یک سازنده داره که مدل یوزر رو میگیره. بعدم داره متدهای UserDetails رو پیاده میکنه که سخت ترینش هم همون متد اولیهس یعنی getAuthorities خب اونجا درواقع داریم از متدهایی که تو مدل یوز نوشتیم که لیست نقشها و مجوزهارو میداد استفاده میکنیم و اونهارو تبدیل به یک لیست میکنیم چون سکیوریتی اونهارو تو یه لیست میخواد با این تفاوت که نقشها حتما اولشون باید _ROLE قرار بگیرد. آبجکتی هم که داخل لیست داریم میذاریم باید از نوع GrantedAuthority باشد. آخرشم لیستی که ساختیم رو برمیگردونیم و تامام. بقیه متد صرفا مپ کردن فیلدهای مدلمون با ویژگیهای UserDetails است. یسری از متدهارم بهشون مقدار ثابت دادیم و درواقع پیادشون نکردیم و بهشون نیازی نداشتیم که حالا شما بسته به کارتون ممکنه بکارتون بیاد.
خب قسمت سختشون رد کردیم و اگه تا الان دووم آوردین میتونم بگم مطلبو تموم کردین چون واسط UserDetailsService چیز خاصی نداره. این واسط درواقع برای اینه که سکیوریتی بدونه چجوری با یوزرنیم کاربر رو پیدا کنه و ما قراره بهش بگیم و حالا میتونید حدس بزنید متد find که تو ریپوزیتوری نوشتیم به چه درد میخوره.
پیاده سازی UserDetailsService به این شکل خواهد بود
@Service public class UserPrincipalDetailService implements UserDetailsService { @Autowired private UserRepo repo; @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { User user = repo.findByUsername(s); if (user == null) throw new UsernameNotFoundException(s); return new UserPrincipal(user); } }
خب اینجا فقط اومدیم گفتیم اگه پیدا نکردی یه اکسپشن پرتاپ کن تا بتونیم ارور نشون کاربر بدیم که اشتباه وارد کرده ورودی هارو و اگر نه مدل یوزر رو پاس بده به کلاسی که ما نوشتیم تا UserDetails رو پیاده کنیم و در نهایت اون رو به عنوان خروجی برگردون که به وسیله اطلاعاتی که ما توی کلاس UserPrincipal قرار دادیم میتونه پسورد و نقش و چیزای دیگه رو چک کنه و درصورت اوکی بودن کاربر رو لاگین کنه و در نهایت همین آبجکتی که ما اینجا ساختیم در session به عنوان یوزر لاگین شده قرار میگیره.
مرحله نهایی اینه که بریم توی کلاس کانفیگ سکیوریتی و بجای تعریف دستی کاربر بگیم که از سرویس ما استفاده کنه که اینجوری میشه :
@Autowired UserPrincipalDetailService detailService; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(detailService); }
دقت کنید که توی کدوم متد configure اینکارو کردم.
و تمام حالا شما میتونید با دیتابیس یوزرهاتونو نگهدارید و البته با هرچیز دیگری و هر نوع دیتابیسی.
سوالی بود اینجا یا توئیتر هستم مرسی از وقتتون.