One easy way to do this without needing to change the pallet code too much, as you said, is using signed extensions. Here's one that does a similar thing:
As for custom origin, you can also do that, but it would require some changes to the pallet code:
// in `trait Config`
type CustomSignedOrigin: EnsureOrigin<Self::AccountId>;
Then, in all of the calls that you want to be restricted:
// remove this
let who = ensure_signed(origin)?;
// add this:
let who = T::CustomSignedOrigin::ensure_origin(origin)?;
Finally, in the top level runtime, you want an implementation of
EnsureOrigin that acts similar to
EnsureSigned but filters out certain accounts.
The main advantage of the second approach is that the overhead will only be felt if the transaction is related to
pallet-democracy, whereas in the former approach, the signed extension is checked against all transaction. Although, if you implement it correctly, the first thing that you will do in the signed extension is to check if the transaction's call is from democracy, and if not, you early exist. In my opinion, this is a very negligible overhead.